diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c456f00e10f7..0e1ed32e174f 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,76 +1,20 @@ -CiviCRM is a community-driven open-source project. It has a small, -full-time "core team" which facilitates development and works on critical -issues. However, many improvements are driven by the active contributors. - -This document provides important information about how to contribute. - -## Review/Release Process - -Releases are developed on a monthly cycle. At the start of the month, the -release-manager will send an invitation to developers who have open PRs, -encouraging them to participate in the release-cycle. Participation -provides a way to exchange feedback with other developers, get PRs merged, -and ensure the next release works -- all with a predictable timeline. - - * For a high-level summary of the release process, see the - [Release Management README](https://github.com/civicrm/release-management/blob/master/README.md). - * For an example invitation, see the previous [invitation for the April-May 2016](https://github.com/civicrm/release-management/issues/1). - -## Pull-Request Scope - -A good pull request (PR) addresses a clearly-defined problem. There should be a detailed description logged in the [issue tracker](http://issues.civicrm.org/). Excellent PRs also increase test coverage. If you are tempted to do additional tweaks or code cleanup outside the scope of that issue, you could make a separate commit and include them in the PR if they are minor & non-controversial, or create a seperate PR if they are more complex. - -There is no size limit for PRs as long as they are focused on completely solving a discreet problem. As a practical matter, though, bigger PRs may take longer to review and merge. When possible, split "epic" issues into bite-sized chunks as long as each seperate PR is functionally complete and does not cause merge conflicts with your other PRs. In the latter case, add commits to an existing PR. - -## Pull-Request Subject - -When filing a pull-request, use a descriptive subject. These are good examples: - - * `CRM-12345 - Fix Paypal IPNs when moon is at half-crescent (waxing)` - * `(WIP) CRM-67890 - Refactor SMS callback endpoint` - * `(NFC) CRM_Utils_PDF - Improve docblocks` - -A few elements to include: - - * **CRM-_XXXXX_** - This is a reference to the [CiviCRM issue tracker](http://issues.civicrm.org/) - (JIRA). A bot will setup crosslinks between JIRA and GitHub. - * **Description** - Provide a brief description of what the pull-request does. - * **(WIP)** - "Work in Progress" - If you are still developing a set of - changes, it may be useful to submit a pull-request and flag it as - `(WIP)`. This allows you to have discussion with other developers and - check test results. Once the change is ready, update the subject line - to remove `(WIP)`. - * **(NFC)** - "Non-Functional Change" - Most patches are designed to - change functionality (e.g. fix an error message or add a new button). - However, some changes are non-functional -- e.g. they cleanup the - code-style, improve the comments, or improve the test-suite. - -## Testing - -Pull-requests are tested automatically by a build-bot. Key things to know: - - * If you are a new contributor, the tests may be placed on hold pending a - cursory review. One of the administrators will post a comment like - `jenkins, ok to test` or `jenkins, add to whitelist`. - * The pull-request will have a colored dot indicating its status: - * **Yellow**: The automated tests are running. - * **Red**: The automated tests have failed. - * **Green**: The automated tests have passed. - * If the automated test fails, click on the red dot to investigate details. Check for information in: - * The initial summary. Ordinarily, this will list test failures and error messages. - * The console output. If the test-suite encountered a significant error (such as a PHP crash), - the key details will only appear in the console. - * Code-style tests are executed first. If the code-style in this patch is inconsistent, the remaining tests will be skipped. - * The primary tests may take 20-120 min to execute. This includes the following suites: `api_v3_AllTests`, `CRM_AllTests`, `Civi\AllTests`, `civicrm-upgrade-test`, and `karma` - * There are a handful of unit tests which are time-sensitive and which fail sporadically. See: https://forum.civicrm.org/index.php?topic=36964.0 - * The web test suite (`WebTest_AllTests`) takes several hours to execute. [It runs separately -- after the PR has been merged.](https://test.civicrm.org/job/CiviCRM-WebTest-Matrix/) - -For detailed discussion about automated tests, see http://wiki.civicrm.org/confluence/display/CRMDOC/Testing - -## Updating a pull-request - -During review, there may be some feedback about problems or additional -changes required for acceptance. If you've never updated a pull-request -before, see [Stackoverflow: How to update a pull request](http://stackoverflow.com/questions/9790448/how-to-update-a-pull-request). - -When you push the update to the pull-request, the test suite will re-execute. +CiviCRM is a community-driven open-source project. It has a small, full-time +[core team](https://civicrm.org/core-team) +which facilitates development and works on critical issues. +Additionally, a large community of active contributors and +[partner organizations](https://civicrm.org/partners-contributors) +drive much of the development work. + +For developers, CiviCRM maintains a comprehensive +[Developer Guide](https://docs.civicrm.org/dev/en/latest). +Topics of particular importance while submitting pull requests include: + +* [Contributing to CiviCRM core](https://docs.civicrm.org/dev/en/latest/core/contributing/) +* [Pull requests](https://docs.civicrm.org/dev/en/latest/tools/git/#pr) +* [Git workflow overview](https://docs.civicrm.org/dev/en/latest/tools/git/#contributing) +* [Writing automated tests](https://docs.civicrm.org/dev/en/latest/testing/setup/) +* [Jenkins continuous integration](https://docs.civicrm.org/dev/en/latest/tools/jenkins/) +* [Release Process](https://docs.civicrm.org/dev/en/latest/core/release-process/) +* [Developer Community](https://docs.civicrm.org/dev/en/latest/basics/community/) + +CiviCRM thanks you for your contributions and invites you to [log your time spent](https://civicrm.org/contributor-log) so that you (or your organization) may receive public recognition and promotion for your efforts. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000000..85e7f8f59d2d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,19 @@ +Overview +---------------------------------------- +_A brief description of the pull request. Try to keep it non-technical._ + +Before +---------------------------------------- +_The current status. Please provide screenshots or gifs ([LICEcap](http://www.cockos.com/licecap/), [SilentCast](https://github.com/colinkeenan/silentcast)) where appropriate._ + +After +---------------------------------------- +_What has been changed. Please provide screenshots or gifs ([LICEcap](http://www.cockos.com/licecap/), [SilentCast](https://github.com/colinkeenan/silentcast)) where appropriate._ + +Technical Details +---------------------------------------- +_If the PR introduces noteworthy technical changes, please describe them here. Provide code snippets if necessary_ + +Comments +---------------------------------------- +_Anything else you would like the reviewer to note_ diff --git a/.gitignore b/.gitignore index 25992eaacb4f..fd86ffe726ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,16 @@ *~ *.bak +.use-civicrm-setup +/ext/ backdrop/ bower_components CRM/Case/xml/configuration CRM/Core/DAO/.listAll.php CRM/Core/DAO/listAll.php -CRM/Core/I18n/SchemaStructure.php bin/setup.conf -civicrm-version.php civicrm-version.txt civicrm.config.php -install/langs.php node_modules -packages/.channels -packages/.depdb -packages/.depdblock -packages/.filemap -packages/.lock -packages/.registry -packages/cache -packages/doc -packages/temp -packages/test settings_location.php sql/case_sample.mysql sql/civicrm.mysql @@ -32,12 +21,12 @@ sql/civicrm_data.mysql sql/civicrm_drop.mysql sql/civicrm_navigation.mysql sql/civicrm_sample.mysql -templates/CRM/common/version.tpl tests/phpunit/CiviTest/CiviSeleniumSettings.php tests/phpunit/CiviTest/civicrm.settings.php tools/stats/config.php authors.txt -drupal/ +/drupal/ +/drupal-8/ WordPress joomla packages/ @@ -54,3 +43,4 @@ civicrm.settings.php sql/dummy_processor.mysql distmaker/distmaker.conf distmaker/out +/tmp diff --git a/.toxic.json b/.toxic.json index 5d1f2a3d854b..d24776f28356 100644 --- a/.toxic.json +++ b/.toxic.json @@ -3,16 +3,79 @@ "toxicAlert": "\"Please:\n\n(Automated notice) This pull-request modifies {SYMBOLS}. That code has been previously identified as hazardous. For advice on dealing with it, please review [Toxic Code Protocol](http://wiki.civicrm.org/confluence/display/CRM/Toxic+Code+Protocol)." }, "checks": { - "CRM_Contact_Import_Parser_Contact::import()": "toxicAlert", - "CRM_Core_BAO_Mapping::buildMappingForm()": "toxicAlert", - "CRM_Event_Form_Participant::postProcess()": "toxicAlert", - "CRM_Export_BAO_Export::exportComponents()": "toxicAlert", - "CRM_Member_Form_Membership::postProcess()": "toxicAlert", - "CRM_Contribute_Form_Contribution::postProcess()": "toxicAlert", + "CRM_Activity_BAO_Activity::create()": "toxicAlert", + "CRM_Activity_Form_Activity::preProcess()": "toxicAlert", + "CRM_Case_BAO_Case::getCaseActivity()": "toxicAlert", + "CRM_Case_BAO_Case::mergeCases()": "toxicAlert", + "CRM_Contact_BAO_Contact::formatProfileContactParams()": "toxicAlert", + "CRM_Contact_BAO_ContactTest::testCreateProfileContact()": "toxicAlert", + "CRM_Contact_BAO_Individual::format()": "toxicAlert", + "CRM_Contact_BAO_Query::addHierarchicalElements()": "toxicAlert", + "CRM_Contact_BAO_Relationship::relatedMemberships()": "toxicAlert", + "CRM_Contact_Form_Contact::postProcess()": "toxicAlert", "CRM_Contact_Import_Form_MapField::buildQuickForm()": "toxicAlert", + "CRM_Contact_Import_Parser::formatCommonData()": "toxicAlert", + "CRM_Contact_Import_Parser::formatContactParameters()": "toxicAlert", + "CRM_Contact_Import_Parser::run()": "toxicAlert", + "CRM_Contact_Import_Parser_Contact::import()": "toxicAlert", + "CRM_Contribute_BAO_Contribution::recordFinancialAccounts()": "toxicAlert", + "CRM_Contribute_BAO_Contribution::transitionComponents()": "toxicAlert", + "CRM_Contribute_BAO_ContributionPage::sendMail()": "toxicAlert", + "CRM_Contribute_BAO_Query::whereClauseSingle()": "toxicAlert", + "CRM_Contribute_Form_Contribution::buildQuickForm()": "toxicAlert", + "CRM_Contribute_Form_Contribution::submit()": "toxicAlert", + "CRM_Contribute_Form_ContributionBase::preProcess()": "toxicAlert", + "CRM_Contribute_Form_ContributionPage_Amount::postProcess()": "toxicAlert", + "CRM_Contribute_Form_Contribution_Confirm::postProcessMembership()": "toxicAlert", + "CRM_Contribute_Form_Contribution_Confirm::processFormSubmission()": "toxicAlert", + "CRM_Contribute_Form_Contribution_Main::buildQuickForm()": "toxicAlert", "CRM_Contribute_Form_Contribution_Main::formRule()": "toxicAlert", + "CRM_Contribute_Form_Contribution_Main::submit()": "toxicAlert", + "CRM_Contribute_Form_Task_Invoice::printPDF()": "toxicAlert", + "CRM_Contribute_Import_Parser::run()": "toxicAlert", + "CRM_Core_BAO_ActionScheduleTest::setUp()": "toxicAlert", + "CRM_Core_BAO_CustomField::formatCustomField()": "toxicAlert", + "CRM_Core_BAO_CustomGroup::getTree()": "toxicAlert", + "CRM_Core_BAO_Mapping::buildMappingForm()": "toxicAlert", + "CRM_Core_BAO_UFGroup::buildProfile()": "toxicAlert", + "CRM_Core_BAO_UFGroup::getValues()": "toxicAlert", + "CRM_Core_I18n_SchemaStructure::widgets()": "toxicAlert", + "CRM_Core_Permission::getEntityActionPermissions()": "toxicAlert", + "CRM_Core_PseudoConstantTest::testOptionValues()": "toxicAlert", + "CRM_Custom_Form_Field::formRule()": "toxicAlert", + "CRM_Dedupe_Merger::getRowsElementsAndInfo()": "toxicAlert", + "CRM_Dedupe_Merger::moveAllBelongings()": "toxicAlert", + "CRM_Event_Form_ManageEvent_Fee::formRule()": "toxicAlert", + "CRM_Event_Form_Participant::submit()": "toxicAlert", "CRM_Event_Form_Registration_Confirm::postProcess()": "toxicAlert", - "CRM_Event_BAO_Event::displayProfile()": "toxicAlert", - "CRM_Contribute_Form_Contribution_Confirm::postProcess()": "toxicAlert" + "CRM_Event_Form_Registration_Register::buildQuickForm()": "toxicAlert", + "CRM_Event_Form_Registration_Register::postProcess()": "toxicAlert", + "CRM_Export_BAO_Export::exportComponents()": "toxicAlert", + "CRM_Mailing_BAO_Mailing::compose()": "toxicAlert", + "CRM_Mailing_BAO_Mailing::getRecipients()": "toxicAlert", + "CRM_Mailing_BAO_Mailing::report()": "toxicAlert", + "CRM_Member_Form_Membership::formRule()": "toxicAlert", + "CRM_Member_Form_Membership::submit()": "toxicAlert", + "CRM_PCP_Page_PCPInfo::run()": "toxicAlert", + "CRM_Price_BAO_PriceField::addQuickFormElement()": "toxicAlert", + "CRM_Price_Form_Field::formRule()": "toxicAlert", + "CRM_Profile_Form::buildQuickForm()": "toxicAlert", + "CRM_Profile_Form::postProcess()": "toxicAlert", + "CRM_Profile_Form::preProcess()": "toxicAlert", + "CRM_Profile_Page_Dynamic::run()": "toxicAlert", + "CRM_Report_Form_Contact_Detail::__construct()": "toxicAlert", + "CRM_Report_Form_Contribute_Bookkeeping::__construct()": "toxicAlert", + "CRM_Report_Form_Contribute_Detail::__construct()": "toxicAlert", + "CRM_Report_Form_Event_ParticipantListing::__construct()": "toxicAlert", + "CRM_Report_Form_Member_ContributionDetail::__construct()": "toxicAlert", + "CRM_UF_Form_Field::buildQuickForm()": "toxicAlert", + "CRM_Upgrade_Incremental_php_FourThree::createFinancialRecords()": "toxicAlert", + "CRM_Utils_Date::relativeToAbsolute()": "toxicAlert", + "CRM_Utils_Mail_EmailProcessor::_process()": "toxicAlert", + "ImportCiviSeleniumTestCase::importContacts()": "toxicAlert", + "WebTest_Contribute_OnBehalfOfOrganization::_testUserWithMoreThanOneRelationship()": "toxicAlert", + "WebTest_Profile_BatchUpdateTest::testBatchUpdate()": "toxicAlert", + "api_v3_JobTest::getMergeLocations()": "toxicAlert", + "api_v3_JobTest::getMergeSets()": "toxicAlert" } } \ No newline at end of file diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 47253a0e1333..376e56ecbac9 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -1,112 +1,248 @@ The following people and organizations sponsored and/or contributed new and improved features to the project. +************************************************ +Code Contributors for 5.x +************************************************ + +CiviCRM - Coleman Watts, Tim Otten + +AGH Strategies - Alice Frumin, Andrew Hunt, Eli Lisseck +Agileware - Francis Whittle, Justin Freeman, Pengyi Zhang +Andrew Thompson +applicado +Australian Greens - Seamus Lee +Bastien Ho +Blackfly Solutions - Alan Dixon +Calibrate - Wannes De Roy +Caltha - Tomasz Pietrzkowski +CEDC - Laryn Kragt Bakker +Chris Burgess +CiviCoop - Jaap Jansma +CiviDesk - Sunil Pawar, Yashodha Chaku +CompuCorp - Camilo Rodriguez, Davi Alexandre, Debarshi Bhaumik, Michael Devery, + Mukesh Ram, Omar Abu Hussein, René Olivo, Vinu Varshith Sekar +Coop SymbioTIC - Mathieu Lutfy, Samuel Vanhove +Davis Media Access - Darrick Servis +Electronic Frontier Foundation - Mark Burdett +eQuality Technology - Greg Rundlett +Freeform Solutions - Herb van den Dool +Fuzion - Jitendra Purohit +Ginkgo Street Labs - Frank Gómez +Hossein Amin +JMA Consulting - Joe Murray, Monish Deb +Johan Vervloet +John Kingsnorth +Joinery - Allen Shaw +Kanzu Code - Carl Andrew Lema +Kompetenzzentrum Technik-Diversity-Chancengleichheit - Niels Heinemann +Left Join Labs - Sean Madsen +Lighthouse Design and Consulting - Brian Shaughnessy +Łukasz Krutul +Megaphone Technology Consulting - Jon Goldberg +MJW Consulting - Matthew Wire +myDropWizard - David Snopek +Naomi Rosenberg +Olivier Tétard +OSSeed Technologies - Madhavi Malgaonkar +Oxfam Germany - Thomas Schüttler, Yuliyana Liyana +Pradeep Nayak +Progressive Technology Project - Jamie McClelland +Richard van Oosterhout +Romain Thouvenin +Squiffle Consulting - Aidan Saunders +Systopia - Björn Endres +Tadpole Collective - Kevin Cristiano +Third Sector Design - Michael McAndrew +Tom Bloor +Wikimedia Foundation - Eileen McNaughton +Wildsight - Lars Sanders-Green +Will Long + ************************************************ Key Contributors and Sponsors for 4.7 ************************************************ -CiviCRM Team - Atif Shaikh, Coleman Watts, David Greenberg, Eileen McNaughton, - Jitendra Purohit, Josh Gowan, Kurund Jalmi, - Michael McAndrew, Monish Deb, Rohan Ramesh Katkar, Tim Otten, - Yashodha Chaku +CiviCRM - Coleman Watts, Josh Gowans, Tim Otten -AGH Strategies - Andrew Hunt, Tyrell Cook, Nikki Murray -Agileware - Francis Whittle +Aaron Jones +Abhikalak Consultants - Amal Sharma +AGH Strategies - Alice Frumin, Andrew Hunt, Eli Lisseck, Nikki Murray, Tommy + Bobo, Tyrell Cook +Agileware - Alok Patel, Francis Whittle, Iris Abarquez, Justin Freeman, Vaibhav + Sagar +András Molnár Andrew West +AppChecker Aputsiaĸ Niels Janussen -Aron Novak -Backoffice Thinking +Arkadiusz Rzadkowolski +Arun Singh +ATD Fourth World - Véronique Gratioulet +Atif Shaikh +Australian Greens - Seamus Lee +Awebon Technologies - Karthikeyan Balasubramanian +BackOffice Thinking - Hassan Farooq Barbara Miller +Black Brick Software - David Hayes +Blackfly Solutions - Alan Dixon +British Humanist Association - Andrew West Borislav Zlatanov Brian Dombrowski +Brooks Digital - Spencer Brooks Caroline Badley -Christian Wach +CEDC - Laryn Kragt Bakker +Chanun Chirattikanon Charlie DeTar +Chirojeugd Vlaanderen - Johan Vervloet +Chris Burgess +Christian Wach Circle Interactive - Dave Jenkins -CiviCoop - Jaap Jansma -CiviDesk - Nicolas Ganivet, Sunil Pawar, Virginie Ganivet -Compucorp - Guanhuan Chen, Jamie Novick -CiviCoop - Jaap Jansma +CiviCoop - Erik Hommel, Jaap Jansma +CiviDesk - Nicolas Ganivet, Sunil Pawar, Virginie Ganivet, Yashodha Chaku +CiviFirst - John Kirk +Community Builders - Dejan Lukic +Community IT Academy - William Mortada +CompuCorp - Camilo Rodriguez, Guanhuan Chen, Kacper Warda, Jamie Novick, Michael + Devery, Mukesh Ram, Omar Abu Hussein Coop SymbioTIC - Mathieu Lutfy, Samuel Vanhove Dave D +Dave Greenberg David Hayes +Deepak Srivastava +Denver DataMan - Steve Kessler Dhanesh Dhuri +Daniël van Vuuren Dmitry Smirnov +Donald Lobo +E-Dynamics - Franky Van Liedekerke +Eaiman Shoshi +Effy Elden +Electronic Frontier Foundation - Mark Burdett Elin Waring -Emphanos LLC - Allen Shaw +Emphanos LLC +Ergon Logic Enterprises - Christopher Gervais +Erich Schulz Esantanche +Francesc Bassas i Bullich Freeform Solutions - Lola Slade, Stephanie Gray, Herb van den Dool Future First - David Knoll, John Prescott -Fuzion NZ - Chris Burgess, Eileen McNaughton, Peter Davis, Torrance Hodgson +Fuzion - Peter Davis, Torrance Hodgson, Jitendra Purohit Giant Rabbit - Peter Haight -Ginkgo Street Labs - Frank Gomez, Galata Tona, Michael Daryabeygi, Roshani Kothari, Toby Lounsbury +Ginkgo Street Labs - Frank Gómez, Galata Tona, Michael Daryabeygi, Roshani + Kothari +Gizra - Aron Novak +Greenleaf Advancement - Guy Iaccarino, Karen Stevenson +Hartmann Computer Consulting - Peter Hartmann +iXiam - Luciano Spiegel Jake Martin White +JazzMan +jernic +Jérôme Lebleu +JMA Consulting - Edsel Lopez, Joe Murray, Monish Deb Joanne Chester Joe McLaughlin -Johan Vervloet -John P Kirk +John Kingsnorth +Joinery - Allen Shaw +Joost Fock Joris -JMA Consulting - Joe Murray, Pradeep Nayak, Edsel Lopez -gah242s -Greenleaf Advancement - Guy Iaccarino -K Sneed Consulting - Kate Sneed +Kanzu Code - Carl Andrew Lema Kemal Bay Ken West Kevin Levie +Kevin Reynen +Klaas Eikelboom +Klangsoft - David Reedy Jr +Kompetenzzentrum Technik-Diversity-Chancengleichheit - Niels Heinemann Korlon - Stuart Gaston -kreynen -Laryn -Lesley Evensen (zorgalina) +K Sneed Consulting - Kate Sneed +Left Join Labs - Sean Madsen +Lemniscus - Noah Miller +Lesley Evensen Lighthouse Consulting and Design - Brian Shaughnessy +Marc Brazeau Marty Wright -Matthew Wire Mattias Michaux +Megaphone Technology Consulting - Jon Goldberg +Michael Hurwicz +Mihael Mladenov +Milton Zurita +MJW Consulting - Matthew Wire Mohit Aggarwal +MongoDB - A. Jesse Jiryu Davis +mountev +myDropWizard - David Snopek +Nathan Brettell National Urban League - Lisa Taliano +Neuwald Tecnologia da Informação - Arthur Almeida Nicholai Burton Niels Heinemann New York City Council New York State Senate - Ken Zalewski +Noah's Light Foundation - Carlos Loeza Northbridge Digital - Oliver Gibson Olaf Buddenhagen -Palante Technology Cooperative - Jon Goldberg, Joseph Lacey +Olivier Hertrich +Oxfam Germany - Thomas Schüttler +Palante Technology Cooperative - Joseph Lacey Paul Campbell -Progressive Tech Project - Alice Aguilar, Jamie McClelland +Pawel Nowak +PowDevel - Beto Aveiga +Pradeep Nayak +Progressive Technology Project - Alice Aguilar, Jamie McClelland +Randy Tobias +Redfin Solutions - Chris Wells Richard Van Oosterhout -RocXa +Rohan Ramesh Katkar +Romain Thouvenin +Rupal Javiya +Samson Alajede Saurabh Batra -Seamus Lee -Seb35 +Sébastien Beyou Semper IT - Karin Gerritsen +Sharique Ahmed Farooqui Shawn Holt -Skvare - Jeremy Proffitt, Peter Petrik +Skvare - Jeremy Proffitt, Mark Hanna, Peter Petrik Smiling Heart Enterprises - Neil Planchon +Spry Digital - Ellen Hendricks Squiffle Consulting - Aidan Saunders +Stan Dragnev Stephen Palmstrom Symbiotic - Mathieu Lutfy, Samuel Vanhove -Systopia - Björn Endres, Niko Bochan -Tadpole - Dana Skallman, Kevin Cristiano +Systopia - Björn Endres, Niko Bochan, Philipp Batroff +Tadpole Collective - Dana Skallman, Kevin Cristiano +Team Expansion - Greg Harris Tech to the People - Xavier Dutoit +Third Sector Design - Michael McAndrew Thomas Leichtuss +Thomas Schüttler Tim Mallezie +Timbsoft Technologies - Tunbola Ogunwande +Tobias Lounsbury Torenware Networks - Rob Thorne -University of Cambridge – Alex Corr, John Kingsnorth -Veda Consulting - Parvez Saleh, Deepak Srivastava, Kajan +University of Cambridge – Alex Corr +Vasantha Kaje +Veda Consulting - Parvez Saleh, Kajan +Vedant Rathore +Vikas Kumar +Vinu Varshith Sekar Wanna Pixel - Nathan Porter, Marisa Porter -Web Access - Sudha Bisht -Wikimedia Foundation - Adam Wight +We Move Europe/Caltha - Tomasz Pietrzkowski +Web Access - Kurund Jalmi, Sudha Bisht +Wikimedia Foundation - Adam Wight, Eileen McNaughton, Elliott Eggleston, Maggie + Epps +Will Long yurg -zarandras ************************************************ Key Contributors and Sponsors for 4.6 ************************************************ CiviCRM Team - Atif Shaikh, Coleman Watts, David Greenberg, Donald Lobo, - Eileen McNaughton, Jitendra Purohit, Josh Gowan, Kurund Jalmi, + Eileen McNaughton, Jitendra Purohit, Josh Gowan, Kurund Jalmi, Michael McAndrew, Monish Deb, Rohan Ramesh Katkar, Tim Otten, Yashodha Chaku -ADG Communications - Steve Binkowski +ADG Communications - Steve Binkowski AGH Strategies - Andrew Hunt, Jane Hanley, Tommy Bobo, Tyrell Cook +Agileware - Justin Freeman, Francis Whittle, Iris Abarquez, Vaibhav Sagar Alex C Allan Chappell Amnesty International Spain - Carlos Capote @@ -116,6 +252,7 @@ Arete Imagine - Marisa Porter, Nate Porter Asylum Hill Congregational Church Blackfly Solutions - Alan Dixon Botanical Society of America - Toby Lounsbury +Chirojeugd Vlaanderen - Johan Vervloet Christian Wach Chris Ward Circle Interactive - Dave Moreton, Andrew Walker, Dave Jenkins, Maya Gibbs @@ -139,7 +276,6 @@ Gnu.org - David Thompson Jaka Kranjc JMA Consulting - Joe Murray, Pradeep Nayak, Edsel Lopez Joanne Chester -Johan Vervloet John Kingsnorth jsnyder83 Kathryn Benedicto diff --git a/CRM/ACL/API.php b/CRM/ACL/API.php index 3337c4ab9b89..80855ac01b65 100644 --- a/CRM/ACL/API.php +++ b/CRM/ACL/API.php @@ -1,9 +1,9 @@ ts('Contact'), 'civicrm_acl_role' => ts('ACL Role'), - ); + ]; } return self::$_entityTable; } @@ -64,12 +64,12 @@ public static function entityTable() { */ public static function objectTable() { if (!self::$_objectTable) { - self::$_objectTable = array( + self::$_objectTable = [ 'civicrm_contact' => ts('Contact'), 'civicrm_group' => ts('Group'), 'civicrm_saved_search' => ts('Contact Group'), 'civicrm_admin' => ts('Import'), - ); + ]; } return self::$_objectTable; } @@ -79,14 +79,14 @@ public static function objectTable() { */ public static function operation() { if (!self::$_operation) { - self::$_operation = array( + self::$_operation = [ 'View' => ts('View'), 'Edit' => ts('Edit'), 'Create' => ts('Create'), 'Delete' => ts('Delete'), 'Search' => ts('Search'), 'All' => ts('All'), - ); + ]; } return self::$_operation; } @@ -94,6 +94,8 @@ public static function operation() { /** * Construct a WHERE clause to handle permissions to $object_* * + * @deprecated + * * @param array $tables * Any tables that may be needed in the FROM. * @param string $operation @@ -115,16 +117,17 @@ public static function permissionClause( $object_table = NULL, $object_id = NULL, $acl_id = NULL, $acl_role = FALSE ) { + CRM_Core_Error::deprecatedFunctionWarning('unknown - this is really old & not used in core'); $dao = new CRM_ACL_DAO_ACL(); - $t = array( + $t = [ 'ACL' => self::getTableName(), 'ACLRole' => 'civicrm_acl_role', 'ACLEntityRole' => CRM_ACL_DAO_EntityRole::getTableName(), 'Contact' => CRM_Contact_DAO_Contact::getTableName(), 'Group' => CRM_Contact_DAO_Group::getTableName(), 'GroupContact' => CRM_Contact_DAO_GroupContact::getTableName(), - ); + ]; $contact_id = CRM_Core_Session::getLoggedInContactID(); @@ -155,7 +158,7 @@ public static function permissionClause( } } - $query = array(); + $query = []; /* Query for permissions granted to all contacts in the domain */ @@ -258,9 +261,9 @@ public static function permissionClause( $dao->query($union); - $allow = array(0); - $deny = array(0); - $override = array(); + $allow = [0]; + $deny = [0]; + $override = []; while ($dao->fetch()) { /* Instant bypass for the following cases: @@ -335,7 +338,7 @@ public static function permissionClause( public static function getClause($table, $id, &$tables) { $table = CRM_Utils_Type::escape($table, 'String'); $id = CRM_Utils_Type::escape($id, 'Integer'); - $whereTables = array(); + $whereTables = []; $ssTable = CRM_Contact_BAO_SavedSearch::getTableName(); @@ -364,7 +367,7 @@ public static function getClause($table, $id, &$tables) { * Assoc. array of the ACL rule's properties */ public function toArray($format = '%s', $hideEmpty = FALSE) { - $result = array(); + $result = []; if (!self::$_fieldKeys) { $fields = CRM_ACL_DAO_ACL::fields(); @@ -394,7 +397,7 @@ public function toArray($format = '%s', $hideEmpty = FALSE) { * Array of assoc. arrays of ACL rules */ public static function &getACLs($contact_id = NULL, $group_id = NULL, $aclRoles = FALSE) { - $results = array(); + $results = []; if (empty($contact_id)) { return $results; @@ -502,7 +505,7 @@ public static function &getACLRoles($contact_id = NULL, $group_id = NULL) { } } - $results = array(); + $results = []; $rule->query($query); @@ -532,7 +535,7 @@ public static function &getGroupACLs($contact_id, $aclRoles = FALSE) { $acl = self::getTableName(); $c2g = CRM_Contact_BAO_GroupContact::getTableName(); $group = CRM_Contact_BAO_Group::getTableName(); - $results = array(); + $results = []; if ($contact_id) { $query = " @@ -600,7 +603,7 @@ public static function &getGroupACLRoles($contact_id) { AND $c2g.contact_id = $contact_id AND $c2g.status = 'Added'"; - $results = array(); + $results = []; $rule->query($query); @@ -641,7 +644,7 @@ public static function &getGroupACLRoles($contact_id) { * Assoc array of ACL rules */ public static function &getAllByContact($contact_id) { - $result = array(); + $result = []; /* First, the contact-specific ACLs, including ACL Roles */ $result += self::getACLs($contact_id, NULL, TRUE); @@ -680,12 +683,13 @@ public static function retrieve(&$params, &$defaults) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { - // note this also resets any ACL cache - CRM_Core_BAO_Cache::deleteGroup('contact fields'); + Civi::cache('fields')->flush(); + // reset ACL and system caches. + CRM_Core_BAO_Cache::resetCaches(); return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active); } @@ -715,7 +719,7 @@ public static function check($str, $contactID) { AND a.object_table = %1 AND a.id IN ( $aclKeys ) "; - $params = array(1 => array($str, 'String')); + $params = [1 => [$str, 'String']]; $count = CRM_Core_DAO::singleValueQuery($query, $params); return ($count) ? TRUE : FALSE; @@ -733,7 +737,7 @@ public static function whereClause($type, &$tables, &$whereTables, $contactID = $acls = CRM_ACL_BAO_Cache::build($contactID); $whereClause = NULL; - $clauses = array(); + $clauses = []; if (!empty($acls)) { $aclKeys = array_keys($acls); @@ -752,12 +756,12 @@ public static function whereClause($type, &$tables, &$whereTables, $contactID = $dao = CRM_Core_DAO::executeQuery($query); // do an or of all the where clauses u see - $ids = array(); + $ids = []; while ($dao->fetch()) { // make sure operation matches the type TODO if (self::matchType($type, $dao->operation)) { if (!$dao->object_id) { - $ids = array(); + $ids = []; $whereClause = ' ( 1 ) '; break; } @@ -774,60 +778,27 @@ public static function whereClause($type, &$tables, &$whereTables, $contactID = AND g.is_active = 1 "; $dao = CRM_Core_DAO::executeQuery($query); - $staticGroupIDs = array(); - $cachedGroupIDs = array(); + $groupIDs = []; + $groupContactCacheClause = FALSE; while ($dao->fetch()) { - // currently operation is restrcited to VIEW/EDIT - if ($dao->where_clause) { - if ($dao->select_tables) { - $tmpTables = array(); - foreach (unserialize($dao->select_tables) as $tmpName => $tmpInfo) { - if ($tmpName == '`civicrm_group_contact-' . $dao->id . '`') { - $tmpName = '`civicrm_group_contact-ACL`'; - $tmpInfo = str_replace('civicrm_group_contact-' . $dao->id, 'civicrm_group_contact-ACL', $tmpInfo); - } - elseif ($tmpName == '`civicrm_group_contact_cache_' . $dao->id . '`') { - $tmpName = '`civicrm_group_contact_cache-ACL`'; - $tmpInfo = str_replace('civicrm_group_contact_cache_' . $dao->id, 'civicrm_group_contact_cache-ACL', $tmpInfo); - } - $tmpTables[$tmpName] = $tmpInfo; - } - $tables = array_merge($tables, - $tmpTables - ); - } - if ($dao->where_tables) { - $tmpTables = array(); - foreach (unserialize($dao->where_tables) as $tmpName => $tmpInfo) { - if ($tmpName == '`civicrm_group_contact-' . $dao->id . '`') { - $tmpName = '`civicrm_group_contact-ACL`'; - $tmpInfo = str_replace('civicrm_group_contact-' . $dao->id, 'civicrm_group_contact-ACL', $tmpInfo); - $staticGroupIDs[] = $dao->id; - } - elseif ($tmpName == '`civicrm_group_contact_cache_' . $dao->id . '`') { - $tmpName = '`civicrm_group_contact_cache-ACL`'; - $tmpInfo = str_replace('civicrm_group_contact_cache_' . $dao->id, 'civicrm_group_contact_cache-ACL', $tmpInfo); - $cachedGroupIDs[] = $dao->id; - } - $tmpTables[$tmpName] = $tmpInfo; - } - $whereTables = array_merge($whereTables, $tmpTables); - } - } + $groupIDs[] = $dao->id; - if (($dao->saved_search_id || $dao->children || $dao->parents) && - $dao->cache_date == NULL - ) { - CRM_Contact_BAO_GroupContactCache::load($dao); + if (($dao->saved_search_id || $dao->children || $dao->parents)) { + if ($dao->cache_date == NULL) { + CRM_Contact_BAO_GroupContactCache::load($dao); + } + $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")"; } - } - if ($staticGroupIDs) { - $clauses[] = '( `civicrm_group_contact-ACL`.group_id IN (' . implode(', ', $staticGroupIDs) . ') AND `civicrm_group_contact-ACL`.status IN ("Added") )'; } - if ($cachedGroupIDs) { - $clauses[] = '`civicrm_group_contact_cache-ACL`.group_id IN (' . implode(', ', $cachedGroupIDs) . ')'; + if ($groupIDs) { + $clauses[] = "( + `contact_a`.id IN ( + SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added' + $groupContactCacheClause + ) + )"; } } } @@ -864,23 +835,28 @@ public static function group( ) { $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups))); if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) { - Civi::$statics[__CLASS__]['permissioned_groups'] = array(); + Civi::$statics[__CLASS__]['permissioned_groups'] = []; } if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) { return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey]; } + if ($allGroups == NULL) { + $allGroups = CRM_Contact_BAO_Contact::buildOptions('group_id', NULL, ['onlyActive' => FALSE]); + } + $acls = CRM_ACL_BAO_Cache::build($contactID); - $ids = array(); + $ids = []; if (!empty($acls)) { $aclKeys = array_keys($acls); $aclKeys = implode(',', $aclKeys); - $cacheKey = "$tableName-$aclKeys"; + $cacheKey = CRM_Utils_Cache::cleanKey("$tableName-$aclKeys"); $cache = CRM_Utils_Cache::singleton(); $ids = $cache->get($cacheKey); if (!$ids) { + $ids = []; $query = " SELECT a.operation, a.object_id FROM civicrm_acl_cache c, civicrm_acl a @@ -891,7 +867,7 @@ public static function group( GROUP BY a.operation,a.object_id ORDER BY a.object_id "; - $params = array(1 => array($tableName, 'String')); + $params = [1 => [$tableName, 'String']]; $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { if ($dao->object_id) { diff --git a/CRM/ACL/BAO/Cache.php b/CRM/ACL/BAO/Cache.php index 392d15f48a66..c165e940c0d6 100644 --- a/CRM/ACL/BAO/Cache.php +++ b/CRM/ACL/BAO/Cache.php @@ -1,9 +1,9 @@ array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; if ($id == 0) { $query .= " OR contact_id IS NULL"; @@ -83,7 +83,7 @@ public static function retrieve($id) { $dao = CRM_Core_DAO::executeQuery($query, $params); - $cache = array(); + $cache = []; while ($dao->fetch()) { $cache[$dao->acl_id] = 1; } @@ -96,7 +96,7 @@ public static function retrieve($id) { */ public static function store($id, &$cache) { foreach ($cache as $aclID => $data) { - $dao = new CRM_ACL_DAO_Cache(); + $dao = new CRM_ACL_BAO_Cache(); if ($id) { $dao->contact_id = $id; } @@ -122,7 +122,7 @@ public static function deleteEntry($id) { DELETE FROM civicrm_acl_cache WHERE contact_id = %1 "; - $params = array(1 => array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; CRM_Core_DAO::executeQuery($query, $params); } @@ -142,6 +142,9 @@ public static function updateEntry($id) { * Deletes all the cache entries. */ public static function resetCache() { + if (!CRM_Core_Config::isPermitCacheFlushMode()) { + return; + } // reset any static caching self::$_cache = NULL; @@ -151,7 +154,12 @@ public static function resetCache() { WHERE modified_date IS NULL OR (modified_date <= %1) "; - $params = array(1 => array(CRM_Contact_BAO_GroupContactCache::getCacheInvalidDateTime(), 'String')); + $params = [ + 1 => [ + CRM_Contact_BAO_GroupContactCache::getCacheInvalidDateTime(), + 'String', + ], + ]; CRM_Core_DAO::singleValueQuery($query, $params); // CRM_Core_DAO::singleValueQuery("TRUNCATE TABLE civicrm_acl_contact_cache"); // No, force-commits transaction diff --git a/CRM/ACL/BAO/EntityRole.php b/CRM/ACL/BAO/EntityRole.php index 2146e0288897..8ed2b2e0b816 100644 --- a/CRM/ACL/BAO/EntityRole.php +++ b/CRM/ACL/BAO/EntityRole.php @@ -1,9 +1,9 @@ ts('Contact'), 'civicrm_group' => ts('Group'), - ); + ]; } return self::$_entityTable; } @@ -80,8 +80,8 @@ public static function retrieve(&$params, &$defaults) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_EntityRole', $id, 'is_active', $is_active); diff --git a/CRM/ACL/DAO/ACL.php b/CRM/ACL/DAO/ACL.php index a4e3359c6dde..d5a62e863ada 100644 --- a/CRM/ACL/DAO/ACL.php +++ b/CRM/ACL/DAO/ACL.php @@ -1,322 +1,329 @@ __table = 'civicrm_acl'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName() , 'entity_id', NULL, 'id', 'entity_table'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName(), 'entity_id', NULL, 'id', 'entity_table'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL ID') , - 'description' => 'Unique table ID', - 'required' => true, + 'title' => ts('ACL ID'), + 'description' => ts('Unique table ID'), + 'required' => TRUE, + 'where' => 'civicrm_acl.id', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'name' => array( + ], + 'name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('ACL Name') , - 'description' => 'ACL Name.', + 'title' => ts('ACL Name'), + 'description' => ts('ACL Name.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_acl.name', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'deny' => array( + ], + ], + 'deny' => [ 'name' => 'deny', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Deny ACL?') , - 'description' => 'Is this ACL entry Allow (0) or Deny (1) ?', - 'required' => true, + 'title' => ts('Deny ACL?'), + 'description' => ts('Is this ACL entry Allow (0) or Deny (1) ?'), + 'required' => TRUE, + 'where' => 'civicrm_acl.deny', + 'default' => '0', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Radio', - ) , - ) , - 'entity_table' => array( + ], + ], + 'entity_table' => [ 'name' => 'entity_table', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('ACL Entity') , - 'description' => 'Table of the object possessing this ACL entry (Contact, Group, or ACL Group)', - 'required' => true, + 'title' => ts('ACL Entity'), + 'description' => ts('Table of the object possessing this ACL entry (Contact, Group, or ACL Group)'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_acl.entity_table', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'entity_id' => array( + ], + 'entity_id' => [ 'name' => 'entity_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Entity ID') , - 'description' => 'ID of the object possessing this ACL', + 'title' => ts('Entity ID'), + 'description' => ts('ID of the object possessing this ACL'), + 'where' => 'civicrm_acl.entity_id', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'operation' => array( + ], + 'operation' => [ 'name' => 'operation', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('ACL Operation') , - 'description' => 'What operation does this ACL entry control?', - 'required' => true, + 'title' => ts('ACL Operation'), + 'description' => ts('What operation does this ACL entry control?'), + 'required' => TRUE, 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_acl.operation', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_ACL_BAO_ACL::operation', - ) - ) , - 'object_table' => array( + ], + ], + 'object_table' => [ 'name' => 'object_table', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('ACL Object') , - 'description' => 'The table of the object controlled by this ACL entry', + 'title' => ts('ACL Object'), + 'description' => ts('The table of the object controlled by this ACL entry'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_acl.object_table', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'object_id' => array( + ], + 'object_id' => [ 'name' => 'object_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL Object ID') , - 'description' => 'The ID of the object controlled by this ACL entry', + 'title' => ts('ACL Object ID'), + 'description' => ts('The ID of the object controlled by this ACL entry'), + 'where' => 'civicrm_acl.object_id', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'acl_table' => array( + ], + 'acl_table' => [ 'name' => 'acl_table', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('ACL Table') , - 'description' => 'If this is a grant/revoke entry, what table are we granting?', + 'title' => ts('ACL Table'), + 'description' => ts('If this is a grant/revoke entry, what table are we granting?'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_acl.acl_table', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'acl_id' => array( + ], + 'acl_id' => [ 'name' => 'acl_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL Group ID') , - 'description' => 'ID of the ACL or ACL group being granted/revoked', + 'title' => ts('ACL Group ID'), + 'description' => ts('ID of the ACL or ACL group being granted/revoked'), + 'where' => 'civicrm_acl.acl_id', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('ACL Is Active?') , - 'description' => 'Is this property active?', + 'title' => ts('ACL Is Active?'), + 'description' => ts('Is this property active?'), + 'where' => 'civicrm_acl.is_active', 'table_name' => 'civicrm_acl', 'entity' => 'ACL', 'bao' => 'CRM_ACL_BAO_ACL', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -324,10 +331,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -335,24 +343,30 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_acl_id' => array( + $indices = [ + 'index_acl_id' => [ 'name' => 'index_acl_id', - 'field' => array( + 'field' => [ 0 => 'acl_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_acl::0::acl_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/ACL/DAO/ACLCache.php b/CRM/ACL/DAO/ACLCache.php new file mode 100644 index 000000000000..cbf774e4b276 --- /dev/null +++ b/CRM/ACL/DAO/ACLCache.php @@ -0,0 +1,222 @@ +__table = 'civicrm_acl_cache'; + parent::__construct(); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'acl_id', 'civicrm_acl', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); + } + return Civi::$statics[__CLASS__]['links']; + } + + /** + * Returns all the column names of this table + * + * @return array + */ + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ + 'name' => 'id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Cache ID'), + 'description' => ts('Unique table ID'), + 'required' => TRUE, + 'where' => 'civicrm_acl_cache.id', + 'table_name' => 'civicrm_acl_cache', + 'entity' => 'ACLCache', + 'bao' => 'CRM_ACL_DAO_ACLCache', + 'localizable' => 0, + ], + 'contact_id' => [ + 'name' => 'contact_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Cache Contact'), + 'description' => ts('Foreign Key to Contact'), + 'where' => 'civicrm_acl_cache.contact_id', + 'table_name' => 'civicrm_acl_cache', + 'entity' => 'ACLCache', + 'bao' => 'CRM_ACL_DAO_ACLCache', + 'localizable' => 0, + 'FKClassName' => 'CRM_Contact_DAO_Contact', + ], + 'acl_id' => [ + 'name' => 'acl_id', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Cache ACL'), + 'description' => ts('Foreign Key to ACL'), + 'required' => TRUE, + 'where' => 'civicrm_acl_cache.acl_id', + 'table_name' => 'civicrm_acl_cache', + 'entity' => 'ACLCache', + 'bao' => 'CRM_ACL_DAO_ACLCache', + 'localizable' => 0, + 'FKClassName' => 'CRM_ACL_DAO_ACL', + ], + 'modified_date' => [ + 'name' => 'modified_date', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Cache Modified Date'), + 'description' => ts('When was this cache entry last modified'), + 'required' => FALSE, + 'where' => 'civicrm_acl_cache.modified_date', + 'table_name' => 'civicrm_acl_cache', + 'entity' => 'ACLCache', + 'bao' => 'CRM_ACL_DAO_ACLCache', + 'localizable' => 0, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); + } + return Civi::$statics[__CLASS__]['fields']; + } + + /** + * Return a mapping from field-name to the corresponding key (as used in fields()). + * + * @return array + * Array(string $name => string $uniqueName). + */ + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); + } + return Civi::$statics[__CLASS__]['fieldKeys']; + } + + /** + * Returns the names of this table + * + * @return string + */ + public static function getTableName() { + return self::$_tableName; + } + + /** + * Returns if this table needs to be logged + * + * @return bool + */ + public function getLog() { + return self::$_log; + } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_cache', $prefix, []); + return $r; + } + + /** + * Returns the list of fields that can be exported + * + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_cache', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_acl_id' => [ + 'name' => 'index_acl_id', + 'field' => [ + 0 => 'acl_id', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_acl_cache::0::acl_id', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; + } + +} diff --git a/CRM/ACL/DAO/Cache.php b/CRM/ACL/DAO/Cache.php deleted file mode 100644 index 16c1af6755b1..000000000000 --- a/CRM/ACL/DAO/Cache.php +++ /dev/null @@ -1,223 +0,0 @@ -__table = 'civicrm_acl_cache'; - parent::__construct(); - } - /** - * Returns foreign keys and entity references. - * - * @return array - * [CRM_Core_Reference_Interface] - */ - static function getReferenceColumns() { - if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'acl_id', 'civicrm_acl', 'id'); - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); - } - return Civi::$statics[__CLASS__]['links']; - } - /** - * Returns all the column names of this table - * - * @return array - */ - static function &fields() { - if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( - 'name' => 'id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Cache ID') , - 'description' => 'Unique table ID', - 'required' => true, - 'table_name' => 'civicrm_acl_cache', - 'entity' => 'Cache', - 'bao' => 'CRM_ACL_BAO_Cache', - 'localizable' => 0, - ) , - 'contact_id' => array( - 'name' => 'contact_id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Cache Contact') , - 'description' => 'Foreign Key to Contact', - 'table_name' => 'civicrm_acl_cache', - 'entity' => 'Cache', - 'bao' => 'CRM_ACL_BAO_Cache', - 'localizable' => 0, - 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'acl_id' => array( - 'name' => 'acl_id', - 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Cache ACL') , - 'description' => 'Foreign Key to ACL', - 'required' => true, - 'table_name' => 'civicrm_acl_cache', - 'entity' => 'Cache', - 'bao' => 'CRM_ACL_BAO_Cache', - 'localizable' => 0, - 'FKClassName' => 'CRM_ACL_DAO_ACL', - ) , - 'modified_date' => array( - 'name' => 'modified_date', - 'type' => CRM_Utils_Type::T_TIMESTAMP, - 'title' => ts('Cache Modified Date') , - 'description' => 'When was this cache entry last modified', - 'required' => false, - 'table_name' => 'civicrm_acl_cache', - 'entity' => 'Cache', - 'bao' => 'CRM_ACL_BAO_Cache', - 'localizable' => 0, - ) , - ); - CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); - } - return Civi::$statics[__CLASS__]['fields']; - } - /** - * Return a mapping from field-name to the corresponding key (as used in fields()). - * - * @return array - * Array(string $name => string $uniqueName). - */ - static function &fieldKeys() { - if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { - Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); - } - return Civi::$statics[__CLASS__]['fieldKeys']; - } - /** - * Returns the names of this table - * - * @return string - */ - static function getTableName() { - return self::$_tableName; - } - /** - * Returns if this table needs to be logged - * - * @return boolean - */ - function getLog() { - return self::$_log; - } - /** - * Returns the list of fields that can be imported - * - * @param bool $prefix - * - * @return array - */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_cache', $prefix, array()); - return $r; - } - /** - * Returns the list of fields that can be exported - * - * @param bool $prefix - * - * @return array - */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_cache', $prefix, array()); - return $r; - } - /** - * Returns the list of indices - */ - public static function indices($localize = TRUE) { - $indices = array( - 'index_acl_id' => array( - 'name' => 'index_acl_id', - 'field' => array( - 0 => 'acl_id', - ) , - 'localizable' => false, - 'sig' => 'civicrm_acl_cache::0::acl_id', - ) , - ); - return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; - } -} diff --git a/CRM/ACL/DAO/EntityRole.php b/CRM/ACL/DAO/EntityRole.php index 5ecda4016a70..ae09968341da 100644 --- a/CRM/ACL/DAO/EntityRole.php +++ b/CRM/ACL/DAO/EntityRole.php @@ -1,203 +1,197 @@ __table = 'civicrm_acl_entity_role'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName() , 'entity_id', NULL, 'id', 'entity_table'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName(), 'entity_id', NULL, 'id', 'entity_table'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Entity Role') , - 'description' => 'Unique table ID', - 'required' => true, + 'title' => ts('Entity Role'), + 'description' => ts('Unique table ID'), + 'required' => TRUE, + 'where' => 'civicrm_acl_entity_role.id', 'table_name' => 'civicrm_acl_entity_role', 'entity' => 'EntityRole', 'bao' => 'CRM_ACL_BAO_EntityRole', 'localizable' => 0, - ) , - 'acl_role_id' => array( + ], + 'acl_role_id' => [ 'name' => 'acl_role_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL Role ID') , - 'description' => 'Foreign Key to ACL Role (which is an option value pair and hence an implicit FK)', - 'required' => true, + 'title' => ts('ACL Role ID'), + 'description' => ts('Foreign Key to ACL Role (which is an option value pair and hence an implicit FK)'), + 'required' => TRUE, + 'where' => 'civicrm_acl_entity_role.acl_role_id', 'table_name' => 'civicrm_acl_entity_role', 'entity' => 'EntityRole', 'bao' => 'CRM_ACL_BAO_EntityRole', 'localizable' => 0, - ) , - 'entity_table' => array( + ], + 'entity_table' => [ 'name' => 'entity_table', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Entity Table') , - 'description' => 'Table of the object joined to the ACL Role (Contact or Group)', - 'required' => true, + 'title' => ts('Entity Table'), + 'description' => ts('Table of the object joined to the ACL Role (Contact or Group)'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_acl_entity_role.entity_table', 'table_name' => 'civicrm_acl_entity_role', 'entity' => 'EntityRole', 'bao' => 'CRM_ACL_BAO_EntityRole', 'localizable' => 0, - ) , - 'entity_id' => array( + ], + 'entity_id' => [ 'name' => 'entity_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL Entity ID') , - 'description' => 'ID of the group/contact object being joined', - 'required' => true, + 'title' => ts('ACL Entity ID'), + 'description' => ts('ID of the group/contact object being joined'), + 'required' => TRUE, + 'where' => 'civicrm_acl_entity_role.entity_id', 'table_name' => 'civicrm_acl_entity_role', 'entity' => 'EntityRole', 'bao' => 'CRM_ACL_BAO_EntityRole', 'localizable' => 0, - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('ACL Entity Role is Active') , - 'description' => 'Is this property active?', + 'title' => ts('ACL Entity Role is Active'), + 'description' => ts('Is this property active?'), + 'where' => 'civicrm_acl_entity_role.is_active', 'table_name' => 'civicrm_acl_entity_role', 'entity' => 'EntityRole', 'bao' => 'CRM_ACL_BAO_EntityRole', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -205,10 +199,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_entity_role', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_entity_role', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -216,33 +211,39 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_entity_role', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_entity_role', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_role' => array( + $indices = [ + 'index_role' => [ 'name' => 'index_role', - 'field' => array( + 'field' => [ 0 => 'acl_role_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_acl_entity_role::0::acl_role_id', - ) , - 'index_entity' => array( + ], + 'index_entity' => [ 'name' => 'index_entity', - 'field' => array( + 'field' => [ 0 => 'entity_table', 1 => 'entity_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_acl_entity_role::0::entity_table::entity_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/ACL/Form/ACL.php b/CRM/ACL/Form/ACL.php index c440178d08e2..7f9419721437 100644 --- a/CRM/ACL/Form/ACL.php +++ b/CRM/ACL/Form/ACL.php @@ -1,9 +1,9 @@ add('text', 'name', ts('Description'), CRM_Core_DAO::getAttribute('CRM_ACL_DAO_ACL', 'name'), TRUE); - $operations = array('' => ts('- select -')) + CRM_ACL_BAO_ACL::operation(); + $operations = ['' => ts('- select -')] + CRM_ACL_BAO_ACL::operation(); $this->add('select', 'operation', ts('Operation'), $operations, TRUE ); - $objTypes = array( + $objTypes = [ '1' => ts('A group of contacts'), '2' => ts('A profile'), '3' => ts('A set of custom data fields'), - ); + ]; if (CRM_Core_Permission::access('CiviEvent')) { $objTypes['4'] = ts('Events'); } - $extra = array('onclick' => "showObjectSelect();"); + $extra = ['onclick' => "showObjectSelect();"]; $this->addRadio('object_type', ts('Type of Data'), $objTypes, @@ -140,31 +140,31 @@ public function buildQuickForm() { ); $label = ts('Role'); - $role = array( + $role = [ '-1' => ts('- select role -'), '0' => ts('Everyone'), - ) + CRM_Core_OptionGroup::values('acl_role'); + ] + CRM_Core_OptionGroup::values('acl_role'); $this->add('select', 'entity_id', $label, $role, TRUE); - $group = array( + $group = [ '-1' => ts('- select -'), '0' => ts('All Groups'), - ) + CRM_Core_PseudoConstant::group(); + ] + CRM_Core_PseudoConstant::group(); - $customGroup = array( + $customGroup = [ '-1' => ts('- select -'), '0' => ts('All Custom Groups'), - ) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id'); + ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id'); - $ufGroup = array( + $ufGroup = [ '-1' => ts('- select -'), '0' => ts('All Profiles'), - ) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id'); + ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id'); - $event = array( + $event = [ '-1' => ts('- select -'), '0' => ts('All Events'), - ) + CRM_Event_PseudoConstant::event(NULL, FALSE, "( is_template IS NULL OR is_template != 1 )"); + ] + CRM_Event_PseudoConstant::event(NULL, FALSE, "( is_template IS NULL OR is_template != 1 )"); $this->add('select', 'group_id', ts('Group'), $group); $this->add('select', 'custom_group_id', ts('Custom Data'), $customGroup); @@ -173,7 +173,7 @@ public function buildQuickForm() { $this->add('checkbox', 'is_active', ts('Enabled?')); - $this->addFormRule(array('CRM_ACL_Form_ACL', 'formRule')); + $this->addFormRule(['CRM_ACL_Form_ACL', 'formRule']); } /** @@ -189,7 +189,7 @@ public static function formRule($params) { $errors['entity_id'] = ts('Please assign this permission to a Role.'); } - $validOperations = array('View', 'Edit'); + $validOperations = ['View', 'Edit']; $operationMessage = ts("Only 'View' and 'Edit' operations are valid for this type of data"); // Figure out which type of object we're permissioning on and make sure user has selected a value. @@ -254,7 +254,9 @@ public static function formRule($params) { */ public function postProcess() { // note this also resets any ACL cache - CRM_Core_BAO_Cache::deleteGroup('contact fields'); + Civi::cache('fields')->flush(); + // reset ACL and system caches. + CRM_Core_BAO_Cache::resetCaches(); if ($this->_action & CRM_Core_Action::DELETE) { CRM_ACL_BAO_ACL::del($this->_id); diff --git a/CRM/ACL/Form/ACLBasic.php b/CRM/ACL/Form/ACLBasic.php index 07b6df7bdad4..263b49592765 100644 --- a/CRM/ACL/Form/ACLBasic.php +++ b/CRM/ACL/Form/ACLBasic.php @@ -1,9 +1,9 @@ _id || $this->_id === '0' @@ -49,9 +49,9 @@ public function setDefaultValues() { WHERE entity_id = %1 AND ( object_table NOT IN ( 'civicrm_saved_search', 'civicrm_uf_group', 'civicrm_custom_group' ) ) "; - $params = array(1 => array($this->_id, 'Integer')); + $params = [1 => [$this->_id, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); - $defaults['object_table'] = array(); + $defaults['object_table'] = []; while ($dao->fetch()) { $defaults['object_table'][$dao->object_table] = 1; } @@ -75,14 +75,14 @@ public function buildQuickForm() { ts('ACL Type'), $permissions, NULL, NULL, TRUE, NULL, - array('', '') + ['', ''] ); $label = ts('Role'); - $role = array( + $role = [ '-1' => ts('- select role -'), '0' => ts('Everyone'), - ) + CRM_Core_OptionGroup::values('acl_role'); + ] + CRM_Core_OptionGroup::values('acl_role'); $entityID = &$this->add('select', 'entity_id', $label, $role, TRUE); if ($this->_id) { @@ -90,7 +90,7 @@ public function buildQuickForm() { } $this->add('checkbox', 'is_active', ts('Enabled?')); - $this->addFormRule(array('CRM_ACL_Form_ACLBasic', 'formRule')); + $this->addFormRule(['CRM_ACL_Form_ACLBasic', 'formRule']); } /** @@ -100,7 +100,7 @@ public function buildQuickForm() { */ public static function formRule($params) { if ($params['entity_id'] == -1) { - $errors = array('entity_id' => ts('Role is a required field')); + $errors = ['entity_id' => ts('Role is a required field')]; return $errors; } @@ -123,7 +123,7 @@ public function postProcess() { WHERE entity_id = %1 AND ( object_table NOT IN ( 'civicrm_saved_search', 'civicrm_uf_group', 'civicrm_custom_group' ) ) "; - $deleteParams = array(1 => array($this->_id, 'Integer')); + $deleteParams = [1 => [$this->_id, 'Integer']]; CRM_Core_DAO::executeQuery($query, $deleteParams); if ($this->_action & CRM_Core_Action::DELETE) { diff --git a/CRM/ACL/Form/EntityRole.php b/CRM/ACL/Form/EntityRole.php index 7d3798d72dbd..1da21f89ea2f 100644 --- a/CRM/ACL/Form/EntityRole.php +++ b/CRM/ACL/Form/EntityRole.php @@ -1,9 +1,9 @@ ts('- select -')) + CRM_Core_OptionGroup::values('acl_role'); + $aclRoles = ['' => ts('- select -')] + CRM_Core_OptionGroup::values('acl_role'); $this->add('select', 'acl_role_id', ts('ACL Role'), $aclRoles, TRUE ); $label = ts('Assigned to'); - $group = array('' => ts('- select group -')) + CRM_Core_PseudoConstant::staticGroup(FALSE, 'Access'); - $this->add('select', 'entity_id', $label, $group, TRUE, array('class' => 'crm-select2 huge')); + $group = ['' => ts('- select group -')] + CRM_Core_PseudoConstant::staticGroup(FALSE, 'Access'); + $this->add('select', 'entity_id', $label, $group, TRUE, ['class' => 'crm-select2 huge']); $this->add('checkbox', 'is_active', ts('Enabled?')); } diff --git a/CRM/ACL/Form/WordPress/Permissions.php b/CRM/ACL/Form/WordPress/Permissions.php index 5273969e0ab1..65191fb9793a 100644 --- a/CRM/ACL/Form/WordPress/Permissions.php +++ b/CRM/ACL/Form/WordPress/Permissions.php @@ -1,9 +1,9 @@ role_names as $role => $name) { - // Dont show the permissions options for administrator, as they have all permissions + // Don't show the permissions options for administrator, as they have all permissions if ($role !== 'administrator') { $roleObj = $wp_roles->get_role($role); if (!empty($roleObj->capabilities)) { @@ -78,25 +77,54 @@ public function buildQuickForm() { $this->setDefaults($defaults); - $descArray = array(); + $descArray = []; foreach ($permissionsDesc as $perm => $attr) { if (count($attr) > 1) { $descArray[$perm] = $attr[1]; } } - $this->assign('permDesc', $descArray); + + // build table rows by merging role perms + $rows = []; + foreach ($rolePerms as $role => $perms) { + foreach ($perms as $name => $title) { + $rows[$name] = $title; + } + } + + // Build array keyed by permission + $table = []; + foreach ($rows as $perm => $label) { + + // Init row with permission label + $table[$perm] = [ + 'label' => $label, + 'roles' => [], + ]; + + // Add permission description and role names + foreach ($roles as $key => $label) { + if (isset($descArray[$perm])) { + $table[$perm]['desc'] = $descArray[$perm]; + } + $table[$perm]['roles'][] = $key; + } + + } + + $this->assign('table', $table); $this->assign('rolePerms', $rolePerms); $this->assign('roles', $roles); $this->addButtons( - array( - array( + [ + [ 'type' => 'next', 'name' => ts('Save'), 'spacing' => '', 'isDefault' => FALSE, - ), - ) + ], + ] ); } @@ -137,16 +165,16 @@ public function postProcess() { $allWarningPermissions[$key] = CRM_Utils_String::munge(strtolower($permission)); } $warningPermissions = array_intersect($allWarningPermissions, array_keys($rolePermissions)); - $warningPermissionNames = array(); + $warningPermissionNames = []; foreach ($warningPermissions as $permission) { $warningPermissionNames[$permission] = $permissionsArray[$permission]; } if (!empty($warningPermissionNames)) { CRM_Core_Session::setStatus( - ts('The %1 role was assigned one or more permissions that may prove dangerous for users of that role to have. Please reconsider assigning %2 to them.', array( - 1 => $wp_roles->role_names[$role], - 2 => implode(', ', $warningPermissionNames), - )), + ts('The %1 role was assigned one or more permissions that may prove dangerous for users of that role to have. Please reconsider assigning %2 to them.', [ + 1 => $wp_roles->role_names[$role], + 2 => implode(', ', $warningPermissionNames), + ]), ts('Unsafe Permission Settings') ); } @@ -163,7 +191,7 @@ public function postProcess() { CRM_Core_Session::setStatus("", ts('Wordpress Access Control Updated'), "success"); - // rebuild the menus to comply with the new permisssions/capabilites + // rebuild the menus to comply with the new permissions/capabilites CRM_Core_Invoke::rebuildMenuAndCaches(); CRM_Utils_System::redirect('admin.php?page=CiviCRM&q=civicrm/admin/access&reset=1'); @@ -186,7 +214,7 @@ public static function getPermissionArray($descriptions = FALSE) { $permissions = CRM_Core_Permission::basicPermissions(FALSE, $descriptions); - $perms_array = array(); + $perms_array = []; foreach ($permissions as $perm => $title) { //order matters here, but we deal with that later $perms_array[CRM_Utils_String::munge(strtolower($perm))] = $title; diff --git a/CRM/ACL/Page/ACL.php b/CRM/ACL/Page/ACL.php index b8c45390fe7f..21ad61efbc41 100644 --- a/CRM/ACL/Page/ACL.php +++ b/CRM/ACL/Page/ACL.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/acl', 'qs' => 'reset=1&action=update&id=%%id%%', 'title' => ts('Edit ACL'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable ACL'), - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable ACL'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/acl', 'qs' => 'reset=1&action=delete&id=%%id%%', 'title' => ts('Delete ACL'), - ), - ); + ], + ]; } return self::$_links; } @@ -90,51 +90,17 @@ public function &links() { /** * Run the page. * - * This method is called after the page is created. It checks for the - * type of action and executes that action. - * Finally it calls the parent's run method. + * Set the breadcrumb before beginning the standard page run. */ public function run() { - // get the requested action - $action = CRM_Utils_Request::retrieve('action', 'String', - // default to 'browse' - $this, FALSE, 'browse' - ); - - // assign vars to templates - $this->assign('action', $action); - $id = CRM_Utils_Request::retrieve('id', 'Positive', - $this, FALSE, 0 - ); - // set breadcrumb to append to admin/access - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'title' => ts('Access Control'), - 'url' => CRM_Utils_System::url('civicrm/admin/access', - 'reset=1' - ), - ), - ); + 'url' => CRM_Utils_System::url('civicrm/admin/access', 'reset=1'), + ], + ]; CRM_Utils_System::appendBreadCrumb($breadCrumb); - // what action to take ? - if ($action & (CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { - $this->edit($action, $id); - } - - if ($action & (CRM_Core_Action::UPDATE)) { - $this->edit($action, $id); - - if (isset($id)) { - $aclName = CRM_Core_DAO::getFieldValue('CRM_ACL_DAO_ACL', $id); - CRM_Utils_System::setTitle(ts('Edit ACL - %1', array(1 => $aclName))); - } - } - - // finally browse the acl's - if ($action & CRM_Core_Action::BROWSE) { - $this->browse(); - } // parent run return parent::run(); @@ -145,7 +111,7 @@ public function run() { */ public function browse() { // get all acl's sorted by weight - $acl = array(); + $acl = []; $query = " SELECT * FROM civicrm_acl @@ -156,26 +122,26 @@ public function browse() { $roles = CRM_Core_OptionGroup::values('acl_role'); - $group = array( + $group = [ '-1' => ts('- select -'), '0' => ts('All Groups'), - ) + CRM_Core_PseudoConstant::group(); - $customGroup = array( + ] + CRM_Core_PseudoConstant::group(); + $customGroup = [ '-1' => ts('- select -'), '0' => ts('All Custom Groups'), - ) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id'); - $ufGroup = array( + ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_CustomField', 'custom_group_id'); + $ufGroup = [ '-1' => ts('- select -'), '0' => ts('All Profiles'), - ) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id'); + ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id'); - $event = array( + $event = [ '-1' => ts('- select -'), '0' => ts('All Events'), - ) + CRM_Event_PseudoConstant::event(); + ] + CRM_Event_PseudoConstant::event(); while ($dao->fetch()) { - $acl[$dao->id] = array(); + $acl[$dao->id] = []; $acl[$dao->id]['name'] = $dao->name; $acl[$dao->id]['operation'] = $dao->operation; $acl[$dao->id]['entity_id'] = $dao->entity_id; @@ -193,22 +159,22 @@ public function browse() { switch ($acl[$dao->id]['object_table']) { case 'civicrm_saved_search': - $acl[$dao->id]['object'] = $group[$acl[$dao->id]['object_id']]; + $acl[$dao->id]['object'] = CRM_Utils_Array::value($acl[$dao->id]['object_id'], $group); $acl[$dao->id]['object_name'] = ts('Group'); break; case 'civicrm_uf_group': - $acl[$dao->id]['object'] = $ufGroup[$acl[$dao->id]['object_id']]; + $acl[$dao->id]['object'] = CRM_Utils_Array::value($acl[$dao->id]['object_id'], $ufGroup); $acl[$dao->id]['object_name'] = ts('Profile'); break; case 'civicrm_custom_group': - $acl[$dao->id]['object'] = $customGroup[$acl[$dao->id]['object_id']]; + $acl[$dao->id]['object'] = CRM_Utils_Array::value($acl[$dao->id]['object_id'], $customGroup); $acl[$dao->id]['object_name'] = ts('Custom Group'); break; case 'civicrm_event': - $acl[$dao->id]['object'] = $event[$acl[$dao->id]['object_id']]; + $acl[$dao->id]['object'] = CRM_Utils_Array::value($acl[$dao->id]['object_id'], $event); $acl[$dao->id]['object_name'] = ts('Event'); break; } @@ -226,7 +192,7 @@ public function browse() { $acl[$dao->id]['action'] = CRM_Core_Action::formLink( self::links(), $action, - array('id' => $dao->id), + ['id' => $dao->id], ts('more'), FALSE, 'ACL.manage.action', @@ -269,4 +235,26 @@ public function userContext($mode = NULL) { return 'civicrm/acl'; } + /** + * Edit an ACL. + * + * @param int $mode + * What mode for the form ?. + * @param int $id + * Id of the entity (for update, view operations). + * @param bool $imageUpload + * Not used in this case, but extended from CRM_Core_Page_Basic. + * @param bool $pushUserContext + * Not used in this case, but extended from CRM_Core_Page_Basic. + */ + public function edit($mode, $id = NULL, $imageUpload = FALSE, $pushUserContext = TRUE) { + if ($mode & (CRM_Core_Action::UPDATE)) { + if (isset($id)) { + $aclName = CRM_Core_DAO::getFieldValue('CRM_ACL_DAO_ACL', $id); + CRM_Utils_System::setTitle(ts('Edit ACL – %1', [1 => $aclName])); + } + } + parent::edit($mode, $id, $imageUpload, $pushUserContext); + } + } diff --git a/CRM/ACL/Page/ACLBasic.php b/CRM/ACL/Page/ACLBasic.php index c536917e2b1a..e55fb919cd81 100644 --- a/CRM/ACL/Page/ACLBasic.php +++ b/CRM/ACL/Page/ACLBasic.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/acl/basic', 'qs' => 'reset=1&action=update&id=%%id%%', 'title' => ts('Edit ACL'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/acl/basic', 'qs' => 'reset=1&action=delete&id=%%id%%', 'title' => ts('Delete ACL'), - ), - ); + ], + ]; } return self::$_links; } @@ -83,37 +83,27 @@ public function &links() { * Finally it calls the parent's run method. */ public function run() { - // get the requested action - $action = CRM_Utils_Request::retrieve('action', 'String', - // default to 'browse' - $this, FALSE, 'browse' - ); - - // assign vars to templates - $this->assign('action', $action); - $id = CRM_Utils_Request::retrieve('id', 'Positive', - $this, FALSE, 0 - ); + $id = $this->getIdAndAction(); // set breadcrumb to append to admin/access - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'title' => ts('Access Control'), 'url' => CRM_Utils_System::url('civicrm/admin/access', 'reset=1'), - ), - ); + ], + ]; CRM_Utils_System::appendBreadCrumb($breadCrumb); // what action to take ? - if ($action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { - $this->edit($action, $id); + if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { + $this->edit($this->_action, $id); } // finally browse the acl's $this->browse(); - // parent run - return parent::run(); + // This replaces parent run, but do parent's parent run + return CRM_Core_Page::run(); } /** @@ -122,7 +112,7 @@ public function run() { public function browse() { // get all acl's sorted by weight - $acl = array(); + $acl = []; $query = " SELECT * FROM civicrm_acl @@ -136,7 +126,7 @@ public function browse() { $permissions = CRM_Core_Permission::basicPermissions(); while ($dao->fetch()) { if (!array_key_exists($dao->entity_id, $acl)) { - $acl[$dao->entity_id] = array(); + $acl[$dao->entity_id] = []; $acl[$dao->entity_id]['name'] = $dao->name; $acl[$dao->entity_id]['entity_id'] = $dao->entity_id; $acl[$dao->entity_id]['entity_table'] = $dao->entity_table; @@ -156,7 +146,7 @@ public function browse() { $acl[$dao->entity_id]['action'] = CRM_Core_Action::formLink( self::links(), $action, - array('id' => $dao->entity_id), + ['id' => $dao->entity_id], ts('more'), FALSE, 'aclRole.manage.action', diff --git a/CRM/ACL/Page/EntityRole.php b/CRM/ACL/Page/EntityRole.php index 32a67e4bc555..d8e70c555b09 100644 --- a/CRM/ACL/Page/EntityRole.php +++ b/CRM/ACL/Page/EntityRole.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/acl/entityrole', 'qs' => 'action=update&id=%%id%%', 'title' => ts('Edit ACL Role Assignment'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable ACL Role Assignment'), - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable ACL Role Assignment'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/acl/entityrole', 'qs' => 'action=delete&id=%%id%%', 'title' => ts('Delete ACL Role Assignment'), - ), - ); + ], + ]; } return self::$_links; } @@ -95,46 +95,35 @@ public function &links() { * Finally it calls the parent's run method. */ public function run() { - // get the requested action - $action = CRM_Utils_Request::retrieve('action', 'String', - // default to 'browse' - $this, FALSE, 'browse' - ); - - // assign vars to templates - $this->assign('action', $action); - $id = CRM_Utils_Request::retrieve('id', 'Positive', - $this, FALSE, 0 - ); + $id = $this->getIdAndAction(); // set breadcrumb to append to admin/access - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'title' => ts('Access Control'), - 'url' => CRM_Utils_System::url('civicrm/admin/access', - 'reset=1' - ), - ), - ); + 'url' => CRM_Utils_System::url('civicrm/admin/access', 'reset=1'), + ], + ]; CRM_Utils_System::appendBreadCrumb($breadCrumb); CRM_Utils_System::setTitle(ts('Assign Users to Roles')); // what action to take ? - if ($action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { - $this->edit($action, $id); + if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { + $this->edit($this->_action, $id); } // reset cache if enabled/disabled - if ($action & (CRM_Core_Action::DISABLE | CRM_Core_Action::ENABLE)) { + if ($this->_action & (CRM_Core_Action::DISABLE | CRM_Core_Action::ENABLE)) { CRM_ACL_BAO_Cache::resetCache(); } // finally browse the acl's - if ($action & CRM_Core_Action::BROWSE) { + if ($this->_action & CRM_Core_Action::BROWSE) { + $this->browse(); } - // parent run - return parent::run(); + // This replaces parent run, but do parent's parent run + return CRM_Core_Page::run(); } /** @@ -143,7 +132,7 @@ public function run() { public function browse() { // get all acl's sorted by weight - $entityRoles = array(); + $entityRoles = []; $dao = new CRM_ACL_DAO_EntityRole(); $dao->find(); @@ -151,7 +140,7 @@ public function browse() { $groups = CRM_Core_PseudoConstant::staticGroup(); while ($dao->fetch()) { - $entityRoles[$dao->id] = array(); + $entityRoles[$dao->id] = []; CRM_Core_DAO::storeValues($dao, $entityRoles[$dao->id]); $entityRoles[$dao->id]['acl_role'] = CRM_Utils_Array::value($dao->acl_role_id, $aclRoles); @@ -169,7 +158,7 @@ public function browse() { $entityRoles[$dao->id]['action'] = CRM_Core_Action::formLink( self::links(), $action, - array('id' => $dao->id), + ['id' => $dao->id], ts('more'), FALSE, 'entityRole.manage.action', diff --git a/CRM/Activity/ActionMapping.php b/CRM/Activity/ActionMapping.php index 26e994b82280..5251972ea9ab 100644 --- a/CRM/Activity/ActionMapping.php +++ b/CRM/Activity/ActionMapping.php @@ -1,9 +1,9 @@ register(CRM_Activity_ActionMapping::create(array( + $registrations->register(CRM_Activity_ActionMapping::create([ 'id' => CRM_Activity_ActionMapping::ACTIVITY_MAPPING_ID, 'entity' => 'civicrm_activity', 'entity_label' => ts('Activity'), @@ -65,7 +64,7 @@ public static function onRegisterActionMappings(\Civi\ActionSchedule\Event\Mappi 'entity_status' => 'activity_status', 'entity_status_label' => ts('Activity Status'), 'entity_date_start' => 'activity_date_time', - ))); + ])); } /** @@ -108,7 +107,7 @@ public function createQuery($schedule, $phase, $defaultParams) { $query['casDateField'] = 'e.activity_date_time'; if (!is_null($schedule->limit_to)) { - $activityContacts = \CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = \CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); if ($schedule->limit_to == 0 || !isset($activityContacts[$schedule->recipient])) { $recipientTypeId = \CRM_Utils_Array::key('Activity Targets', $activityContacts); } diff --git a/CRM/Activity/BAO/Activity.php b/CRM/Activity/BAO/Activity.php index 9784aff36342..2a999323cd61 100644 --- a/CRM/Activity/BAO/Activity.php +++ b/CRM/Activity/BAO/Activity.php @@ -1,9 +1,9 @@ copyValues($params); if ($activity->find(TRUE)) { - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); @@ -199,9 +207,9 @@ public static function deleteActivity(&$params, $moveToTrash = FALSE) { // CRM-4525 log activity delete $logMsg = 'Case Activity deleted for'; - $msgs = array(); + $msgs = []; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -228,10 +236,10 @@ public static function deleteActivity(&$params, $moveToTrash = FALSE) { // delete the recently created Activity if ($result) { - $activityRecent = array( + $activityRecent = [ 'id' => $activity->id, 'type' => 'Activity', - ); + ]; CRM_Utils_Recent::del($activityRecent); } @@ -277,6 +285,10 @@ public static function deleteActivityContact($activityId, $recordTypeID = NULL) * @return CRM_Activity_BAO_Activity|null|object */ public static function create(&$params) { + // CRM-20958 - These fields are managed by MySQL triggers. Watch out for clients resaving stale timestamps. + unset($params['created_date']); + unset($params['modified_date']); + // check required params if (!self::dataExists($params)) { throw new CRM_Core_Exception('Not enough data to create activity object'); @@ -292,17 +304,17 @@ public static function create(&$params) { if (isset($params['activity_date_time']) && strcmp($params['activity_date_time'], CRM_Utils_Date::processDate(date('Ymd')) == -1) ) { - $params['status_id'] = 2; + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'); } else { - $params['status_id'] = 1; + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'); } } // Set priority to Normal for Auto-populated activities (for Cases) - if (CRM_Utils_Array::value('priority_id', $params) === NULL && + if (!isset($params['priority_id']) && // if not set and not 0 - !CRM_Utils_Array::value('id', $params) + empty($params['id']) ) { $priority = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id'); $params['priority_id'] = array_search('Normal', $priority); @@ -317,7 +329,7 @@ public static function create(&$params) { // CRM-9137 if (!empty($params['id'])) { - CRM_Utils_Hook::pre('edit', 'Activity', $activity->id, $params); + CRM_Utils_Hook::pre('edit', 'Activity', $params['id'], $params); } else { CRM_Utils_Hook::pre('create', 'Activity', NULL, $params); @@ -344,17 +356,16 @@ public static function create(&$params) { } $activityId = $activity->id; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); - $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); - $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); + $sourceID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Source'); + $assigneeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Assignees'); + $targetID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'); if (isset($params['source_contact_id'])) { - $acParams = array( + $acParams = [ 'activity_id' => $activityId, 'contact_id' => $params['source_contact_id'], 'record_type_id' => $sourceID, - ); + ]; self::deleteActivityContact($activityId, $sourceID); CRM_Activity_BAO_ActivityContact::create($acParams); } @@ -366,7 +377,7 @@ public static function create(&$params) { $resultAssignment = NULL; if (!empty($params['assignee_contact_id'])) { - $assignmentParams = array('activity_id' => $activityId); + $assignmentParams = ['activity_id' => $activityId]; if (is_array($params['assignee_contact_id'])) { if (CRM_Utils_Array::value('deleteActivityAssignment', $params, TRUE)) { @@ -376,11 +387,11 @@ public static function create(&$params) { foreach ($params['assignee_contact_id'] as $acID) { if ($acID) { - $assigneeParams = array( + $assigneeParams = [ 'activity_id' => $activityId, 'contact_id' => $acID, 'record_type_id' => $assigneeID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($assigneeParams); } } @@ -419,8 +430,8 @@ public static function create(&$params) { $resultTarget = NULL; if (!empty($params['target_contact_id'])) { - $targetParams = array('activity_id' => $activityId); - $resultTarget = array(); + $targetParams = ['activity_id' => $activityId]; + $resultTarget = []; if (is_array($params['target_contact_id'])) { if (CRM_Utils_Array::value('deleteActivityTarget', $params, TRUE)) { // first delete existing targets if any @@ -429,11 +440,11 @@ public static function create(&$params) { foreach ($params['target_contact_id'] as $tid) { if ($tid) { - $targetContactParams = array( + $targetContactParams = [ 'activity_id' => $activityId, 'contact_id' => $tid, 'record_type_id' => $targetID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($targetContactParams); } } @@ -472,7 +483,7 @@ public static function create(&$params) { $logMsg = "Activity created for "; } - $msgs = array(); + $msgs = []; if (isset($params['source_contact_id'])) { $msgs[] = "source={$params['source_contact_id']}"; } @@ -518,7 +529,7 @@ public static function create(&$params) { $transaction->commit(); if (empty($params['skipRecentView'])) { - $recentOther = array(); + $recentOther = []; if (!empty($params['case_id'])) { $caseContactID = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseContact', $params['case_id'], 'contact_id', 'case_id'); $url = CRM_Utils_System::url('civicrm/case/activity/view', @@ -559,7 +570,7 @@ public static function create(&$params) { if (!isset($activity->parent_id)) { $recentContactDisplay = CRM_Contact_BAO_Contact::displayName($recentContactId); // add the recently created Activity - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE); + $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id'); $activitySubject = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activity->id, 'subject'); $title = ""; @@ -585,30 +596,35 @@ public static function create(&$params) { CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); - if (!empty($params['id'])) { - CRM_Utils_Hook::post('edit', 'Activity', $activity->id, $activity); - } - else { - CRM_Utils_Hook::post('create', 'Activity', $activity->id, $activity); - } - // if the subject contains a ‘[case #…]’ string, file that activity on the related case (CRM-5916) - $matches = array(); - if (preg_match('/\[case #([0-9a-h]{7})\]/', CRM_Utils_Array::value('subject', $params), $matches)) { + $matches = []; + $subjectToMatch = CRM_Utils_Array::value('subject', $params); + if (preg_match('/\[case #([0-9a-h]{7})\]/', $subjectToMatch, $matches)) { $key = CRM_Core_DAO::escapeString(CIVICRM_SITE_KEY); $hash = $matches[1]; - $query = "SELECT id FROM civicrm_case WHERE SUBSTR(SHA1(CONCAT('$key', id)), 1, 7) = '$hash'"; - $caseParams = array( + $query = "SELECT id FROM civicrm_case WHERE SUBSTR(SHA1(CONCAT('$key', id)), 1, 7) = '" . CRM_Core_DAO::escapeString($hash) . "'"; + } + elseif (preg_match('/\[case #(\d+)\]/', $subjectToMatch, $matches)) { + $query = "SELECT id FROM civicrm_case WHERE id = '" . CRM_Core_DAO::escapeString($matches[1]) . "'"; + } + if (!empty($matches)) { + $caseParams = [ 'activity_id' => $activity->id, 'case_id' => CRM_Core_DAO::singleValueQuery($query), - ); + ]; if ($caseParams['case_id']) { CRM_Case_BAO_Case::processCaseActivity($caseParams); } else { - self::logActivityAction($activity, "unknown case hash encountered: $hash"); + self::logActivityAction($activity, "Case details for {$matches[1]} not found while recording an activity on case."); } } + if (!empty($params['id'])) { + CRM_Utils_Hook::post('edit', 'Activity', $activity->id, $activity); + } + else { + CRM_Utils_Hook::post('create', 'Activity', $activity->id, $activity); + } return $result; } @@ -626,17 +642,17 @@ public static function create(&$params) { public static function logActivityAction($activity, $logMessage = NULL) { $id = CRM_Core_Session::getLoggedInContactID(); if (!$id) { - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $id = self::getActivityContact($activity->id, $sourceID); } - $logParams = array( + $logParams = [ 'entity_table' => 'civicrm_activity', 'entity_id' => $activity->id, 'modified_id' => $id, 'modified_date' => date('YmdHis'), 'data' => $logMessage, - ); + ]; CRM_Core_BAO_Log::add($logParams); return TRUE; } @@ -655,87 +671,22 @@ public static function logActivityAction($activity, $logMessage = NULL) { * - caseId int case ID * - context string page on which selector is build * - activity_type_id int|string the activitiy types we want to restrict by - * @param bool $getCount - * Get count of the activities * - * @return array|int + * @return array * Relevant data object values of open activities + * @throws \CiviCRM_API3_Exception */ - public static function getActivities($params, $getCount = FALSE) { - $activities = array(); - - // fetch all active activity types - $activityTypes = CRM_Core_OptionGroup::values('activity_type'); + public static function getActivities($params) { + $activities = []; // Activity.Get API params - $activityParams = array( - 'is_deleted' => 0, - 'is_current_revision' => 1, - 'is_test' => 0, - 'contact_id' => CRM_Utils_Array::value('contact_id', $params), - 'return' => array( - 'activity_date_time', - 'source_record_id', - 'source_contact_id', - 'source_contact_name', - 'assignee_contact_id', - 'target_contact_id', - 'target_contact_name', - 'assignee_contact_name', - 'status_id', - 'subject', - 'activity_type_id', - 'activity_type', - 'case_id', - 'campaign_id', - ), - 'check_permissions' => 1, - 'options' => array( - 'offset' => CRM_Utils_Array::value('offset', $params, 0), - ), - ); - - if ($params['context'] != 'activity') { - $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey(__CLASS__, 'status_id', 'Scheduled'); - } - - // activity type ID clause - if (!empty($params['activity_type_id'])) { - if (is_array($params['activity_type_id'])) { - foreach ($params['activity_type_id'] as $idx => $value) { - $params['activity_type_id'][$idx] = CRM_Utils_Type::escape($value, 'Positive'); - } - $activityParams['activity_type_id'] = array('IN' => $params['activity_type_id']); - } - else { - $activityParams['activity_type_id'] = CRM_Utils_Type::escape($params['activity_type_id'], 'Positive'); - } - } - elseif (!empty($activityTypes) && count($activityTypes)) { - $activityParams['activity_type_id'] = array('IN' => array_keys($activityTypes)); - } - - $excludeActivityIDs = array(); - if (!empty($params['activity_type_exclude_id'])) { - if (is_array($params['activity_type_exclude_id'])) { - foreach ($params['activity_type_exclude_id'] as $idx => $value) { - $excludeActivityIDs[$idx] = CRM_Utils_Type::escape($value, 'Positive'); - } - } - else { - $excludeActivityIDs[] = CRM_Utils_Type::escape($params['activity_type_exclude_id'], 'Positive'); - } - } + $activityParams = self::getActivityParamsForDashboardFunctions($params); if (!empty($params['rowCount']) && $params['rowCount'] > 0 ) { $activityParams['options']['limit'] = $params['rowCount']; } - // set limit = 0 if we need to fetch the activity count - elseif ($getCount) { - $activityParams['options']['limit'] = 0; - } if (!empty($params['sort'])) { if (is_a($params['sort'], 'CRM_Utils_Sort')) { @@ -746,23 +697,31 @@ public static function getActivities($params, $getCount = FALSE) { } } - if (empty($order)) { - // context = 'activity' in Activities tab. - $activityParams['options']['sort'] = (CRM_Utils_Array::value('context', $params) == 'activity') ? "activity_date_time DESC" : "status_id ASC, activity_date_time ASC"; - } - else { - $activityParams['options']['sort'] = str_replace('activity_type ', 'activity_type_id.label ', $order); - } + $activityParams['options']['sort'] = empty($order) ? "activity_date_time DESC" : str_replace('activity_type ', 'activity_type_id.label ', $order); - //TODO : - // 1. we should use Activity.Getcount for fetching count only, but in order to check that - // current logged in user has permission to view Case activities we are performing filtering out those activities from list (see below). - // This logic need to be incorporated in Activity.get definition - $result = civicrm_api3('Activity', 'Get', $activityParams); + $activityParams['return'] = [ + 'activity_date_time', + 'source_record_id', + 'source_contact_id', + 'source_contact_name', + 'assignee_contact_id', + 'assignee_contact_name', + 'status_id', + 'subject', + 'activity_type_id', + 'activity_type', + 'case_id', + 'campaign_id', + ]; + foreach (['case_id' => 'CiviCase', 'campaign_id' => 'CiviCampaign'] as $attr => $component) { + if (in_array($component, self::activityComponents())) { + $activityParams['return'][] = $attr; + } + } + $result = civicrm_api3('Activity', 'Get', $activityParams)['values']; - $enabledComponents = self::activityComponents(); + $bulkActivityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Bulk Email'); $allCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE); - $bulkActivityTypeID = CRM_Core_PseudoConstant::getKey(__CLASS__, 'activity_type_id', 'Bulk Email'); // CRM-3553, need to check user has access to target groups. $mailingIDs = CRM_Mailing_BAO_Mailing::mailingACLIDs(); @@ -770,46 +729,83 @@ public static function getActivities($params, $getCount = FALSE) { (CRM_Mailing_Info::workflowEnabled() && CRM_Core_Permission::check('create mailings')) ); - $mappingParams = array( - 'id' => 'activity_id', + // @todo - get rid of this & just handle in the array declaration like we do with 'subject' etc. + $mappingParams = [ 'source_record_id' => 'source_record_id', 'activity_type_id' => 'activity_type_id', - 'activity_date_time' => 'activity_date_time', 'status_id' => 'status_id', - 'subject' => 'subject', 'campaign_id' => 'campaign_id', - 'assignee_contact_name' => 'assignee_contact_name', - 'target_contact_name' => 'target_contact_name', - 'source_contact_id' => 'source_contact_id', - 'source_contact_name' => 'source_contact_name', 'case_id' => 'case_id', - ); + ]; - foreach ($result['values'] as $id => $activity) { - // skip case activities if CiviCase is not enabled OR those actvities which are - if ((!empty($activity['case_id']) && !in_array('CiviCase', $enabledComponents)) || - (count($excludeActivityIDs) && in_array($activity['activity_type_id'], $excludeActivityIDs)) - ) { - continue; + if (empty($result)) { + $targetCount = []; + } + else { + $targetCount = CRM_Core_DAO::executeQuery(' + SELECT activity_id, count(*) as target_contact_count + FROM civicrm_activity_contact + INNER JOIN civicrm_contact c ON contact_id = c.id AND c.is_deleted = 0 + WHERE activity_id IN (' . implode(',', array_keys($result)) . ') + AND record_type_id = %1 + GROUP BY activity_id', [ + 1 => [ + CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'), + 'Integer', + ], + ])->fetchAll(); + } + foreach ($targetCount as $activityTarget) { + $result[$activityTarget['activity_id']]['target_contact_count'] = $activityTarget['target_contact_count']; + } + // Iterate through & do basic mappings & determine which ones we want to retrieve target count for. + foreach ($result as $id => $activity) { + $activities[$id] = [ + 'activity_id' => $activity['id'], + 'activity_date_time' => CRM_Utils_Array::value('activity_date_time', $activity), + 'subject' => CRM_Utils_Array::value('subject', $activity), + 'assignee_contact_name' => CRM_Utils_Array::value('assignee_contact_sort_name', $activity, []), + 'source_contact_id' => CRM_Utils_Array::value('source_contact_id', $activity), + 'source_contact_name' => CRM_Utils_Array::value('source_contact_sort_name', $activity), + ]; + $activities[$id]['activity_type_name'] = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $activity['activity_type_id']); + $activities[$id]['activity_type'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $activity['activity_type_id']); + $activities[$id]['target_contact_count'] = CRM_Utils_Array::value('target_contact_count', $activity, 0); + if (!empty($activity['target_contact_count'])) { + $displayedTarget = civicrm_api3('ActivityContact', 'get', [ + 'activity_id' => $id, + 'check_permissions' => TRUE, + 'options' => ['limit' => 1], + 'record_type_id' => 'Activity Targets', + 'return' => ['contact_id.sort_name', 'contact_id'], + 'sequential' => 1, + ])['values']; + if (empty($displayedTarget[0])) { + $activities[$id]['target_contact_name'] = []; + } + else { + $activities[$id]['target_contact_name'] = [$displayedTarget[0]['contact_id'] => $displayedTarget[0]['contact_id.sort_name']]; + } } - - $activities[$id] = array(); - - // if count is needed, no need to populate the array list with attributes - if ($getCount) { - continue; + if ($activities[$id]['activity_type_name'] === 'Bulk Email') { + $bulkActivities[] = $id; + // Get the total without permissions being passed but only display names after permissioning. + $activities[$id]['recipients'] = ts('(%1 recipients)', [1 => $activities[$id]['target_contact_count']]); } + } - $isBulkActivity = (!$bulkActivityTypeID || ($bulkActivityTypeID != $activity['activity_type_id'])); + // Eventually this second iteration should just handle the target contacts. It's a bit muddled at + // the moment as the bulk activity stuff needs unravelling & test coverage. + foreach ($result as $id => $activity) { + $isBulkActivity = (!$bulkActivityTypeID || ($bulkActivityTypeID === $activity['activity_type_id'])); foreach ($mappingParams as $apiKey => $expectedName) { - if (in_array($apiKey, array('assignee_contact_name', 'target_contact_name'))) { - $activities[$id][$expectedName] = CRM_Utils_Array::value($apiKey, $activity, array()); - if ($apiKey == 'target_contact_name' && count($activity['target_contact_name'])) { - $activities[$id]['target_contact_counter'] = count($activity['target_contact_name']); - } + if (in_array($apiKey, [ + 'target_contact_name', + ])) { if ($isBulkActivity) { - $activities[$id]['recipients'] = ts('(%1 recipients)', array(1 => count($activity['target_contact_name']))); + // @todo - how is this used? Couldn't we use 'is_bulk' or something clearer? + // or the calling function could handle $activities[$id]['mailingId'] = FALSE; if ($accessCiviMail && ($mailingIDs === TRUE || in_array($activity['source_record_id'], $mailingIDs)) @@ -828,11 +824,9 @@ public static function getActivities($params, $getCount = FALSE) { } } else { + // @todo this generic assign could just be handled in array declaration earlier. $activities[$id][$expectedName] = CRM_Utils_Array::value($apiKey, $activity); - if ($apiKey == 'activity_type_id') { - $activities[$id]['activity_type'] = CRM_Utils_Array::value($activities[$id][$expectedName], $activityTypes); - } - elseif ($apiKey == 'campaign_id') { + if ($apiKey == 'campaign_id') { $activities[$id]['campaign'] = CRM_Utils_Array::value($activities[$id][$expectedName], $allCampaigns); } } @@ -846,285 +840,113 @@ public static function getActivities($params, $getCount = FALSE) { $activities[$id]['is_recurring_activity'] = CRM_Core_BAO_RecurringEntity::getParentFor($id, 'civicrm_activity'); } - return $getCount ? count($activities) : $activities; + return $activities; } /** - * Get the list Activities. - * - * @deprecated - * - * @todo - use the api for this - this is working but have temporarily backed out - * due to performance issue to be resolved - CRM-20481. - * - * @param array $input - * Array of parameters. - * Keys include - * - contact_id int contact_id whose activities we want to retrieve - * - offset int which row to start from ? - * - rowCount int how many rows to fetch - * - sort object|array object or array describing sort order for sql query. - * - admin boolean if contact is admin - * - caseId int case ID - * - context string page on which selector is build - * - activity_type_id int|string the activitiy types we want to restrict by + * Filter the activity types to only return the ones we actually asked for + * Uses params['activity_type_id'] and params['activity_type_exclude_id'] * - * @return array - * Relevant data object values of open activities + * @param $params + * @return array|null (Use in Activity.get API activity_type_id) */ - public static function deprecatedGetActivities($input) { - // Step 1: Get the basic activity data. - $bulkActivityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', - 'activity_type_id', - 'Bulk Email' - ); - - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); - $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); - $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); - - $config = CRM_Core_Config::singleton(); - - $randomNum = md5(uniqid()); - $activityTempTable = "civicrm_temp_activity_details_{$randomNum}"; - - $tableFields = array( - 'activity_id' => 'int unsigned', - 'activity_date_time' => 'datetime', - 'source_record_id' => 'int unsigned', - 'status_id' => 'int unsigned', - 'subject' => 'varchar(255)', - 'source_contact_name' => 'varchar(255)', - 'activity_type_id' => 'int unsigned', - 'activity_type' => 'varchar(128)', - 'case_id' => 'int unsigned', - 'case_subject' => 'varchar(255)', - 'campaign_id' => 'int unsigned', - ); + public static function filterActivityTypes($params) { + $activityTypes = []; - $sql = "CREATE TEMPORARY TABLE {$activityTempTable} ( "; - $insertValueSQL = array(); - // The activityTempTable contains the sorted rows - // so in order to maintain the sort order as-is we add an auto_increment - // field; we can sort by this later to ensure the sort order stays correct. - $sql .= " fixed_sort_order INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,"; - foreach ($tableFields as $name => $desc) { - $sql .= "$name $desc,\n"; - $insertValueSQL[] = $name; - } - - // add unique key on activity_id just to be sure - // this cannot be primary key because we need that for the auto_increment - // fixed_sort_order field - $sql .= " - UNIQUE KEY ( activity_id ) - ) ENGINE=HEAP DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci - "; - - CRM_Core_DAO::executeQuery($sql); - - $insertSQL = "INSERT INTO {$activityTempTable} (" . implode(',', $insertValueSQL) . " ) "; - - $order = $limit = $groupBy = ''; - $groupBy = " GROUP BY tbl.activity_id, tbl.activity_type, tbl.case_id, tbl.case_subject "; - - if (!empty($input['sort'])) { - if (is_a($input['sort'], 'CRM_Utils_Sort')) { - $orderBy = $input['sort']->orderBy(); - if (!empty($orderBy)) { - $order = " ORDER BY $orderBy"; - } - } - elseif (trim($input['sort'])) { - $sort = CRM_Utils_Type::escape($input['sort'], 'String'); - $order = " ORDER BY $sort "; - } - } - - if (empty($order)) { - // context = 'activity' in Activities tab. - $order = (CRM_Utils_Array::value('context', $input) == 'activity') ? " ORDER BY tbl.activity_date_time desc " : " ORDER BY tbl.status_id asc, tbl.activity_date_time asc "; - } - - if (!empty($input['rowCount']) && - $input['rowCount'] > 0 - ) { - $limit = " LIMIT {$input['offset']}, {$input['rowCount']} "; + // If no activity types are specified, get all the active ones + if (empty($params['activity_type_id'])) { + $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'get'); } - $input['count'] = FALSE; - list($sqlClause, $params) = self::deprecatedGetActivitySQLClause($input); - - $query = "{$insertSQL} - SELECT DISTINCT tbl.* from ( {$sqlClause} ) -as tbl "; - - // Filter case activities - CRM-5761. - $components = self::activityComponents(); - if (!in_array('CiviCase', $components)) { - $query .= " -LEFT JOIN civicrm_case_activity ON ( civicrm_case_activity.activity_id = tbl.activity_id ) - WHERE civicrm_case_activity.id IS NULL"; - } - - $query = $query . $groupBy . $order . $limit; - - $dao = CRM_Core_DAO::executeQuery($query, $params); - - // step 2: Get target and assignee contacts for above activities - // create temp table for target contacts - $activityContactTempTable = "civicrm_temp_activity_contact_{$randomNum}"; - $query = "CREATE TEMPORARY TABLE {$activityContactTempTable} ( - activity_id int unsigned, contact_id int unsigned, record_type_id varchar(16), - contact_name varchar(255), is_deleted int unsigned, counter int unsigned, INDEX index_activity_id( activity_id ) ) - ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci"; - - CRM_Core_DAO::executeQuery($query); - - // note that we ignore bulk email for targets, since we don't show it in selector - $query = " -INSERT INTO {$activityContactTempTable} ( activity_id, contact_id, record_type_id, contact_name, is_deleted ) -SELECT ac.activity_id, - ac.contact_id, - ac.record_type_id, - c.sort_name, - c.is_deleted -FROM {$activityTempTable} -INNER JOIN civicrm_activity a ON ( a.id = {$activityTempTable}.activity_id ) -INNER JOIN civicrm_activity_contact ac ON ( ac.activity_id = {$activityTempTable}.activity_id ) -INNER JOIN civicrm_contact c ON c.id = ac.contact_id -WHERE ac.record_type_id != %1 -"; - $params = array(1 => array($targetID, 'Integer')); - CRM_Core_DAO::executeQuery($query, $params); - - $activityFields = array("ac.activity_id", "ac.contact_id", "ac.record_type_id", "c.sort_name", "c.is_deleted"); - $select = CRM_Contact_BAO_Query::appendAnyValueToSelect($activityFields, "ac.activity_id"); - - // for each activity insert one target contact - // if we load all target contacts the performance will suffer a lot for mass-activities. - $query = " -INSERT INTO {$activityContactTempTable} ( activity_id, contact_id, record_type_id, contact_name, is_deleted, counter ) -{$select}, count(ac.contact_id) -FROM {$activityTempTable} -INNER JOIN civicrm_activity a ON ( a.id = {$activityTempTable}.activity_id ) -INNER JOIN civicrm_activity_contact ac ON ( ac.activity_id = {$activityTempTable}.activity_id ) -INNER JOIN civicrm_contact c ON c.id = ac.contact_id -WHERE ac.record_type_id = %1 -GROUP BY ac.activity_id -"; - - CRM_Core_DAO::executeQuery($query, $params); - - // step 3: Combine all temp tables to get final query for activity selector - // sort by the original sort order, stored in fixed_sort_order - $query = " -SELECT {$activityTempTable}.*, - {$activityContactTempTable}.contact_id, - {$activityContactTempTable}.record_type_id, - {$activityContactTempTable}.contact_name, - {$activityContactTempTable}.is_deleted, - {$activityContactTempTable}.counter, - re.parent_id as is_recurring_activity -FROM {$activityTempTable} -INNER JOIN {$activityContactTempTable} on {$activityTempTable}.activity_id = {$activityContactTempTable}.activity_id -LEFT JOIN civicrm_recurring_entity re on {$activityContactTempTable}.activity_id = re.entity_id -ORDER BY fixed_sort_order - "; - - $dao = CRM_Core_DAO::executeQuery($query); - - // CRM-3553, need to check user has access to target groups. - $mailingIDs = CRM_Mailing_BAO_Mailing::mailingACLIDs(); - $accessCiviMail = ( - (CRM_Core_Permission::check('access CiviMail')) || - (CRM_Mailing_Info::workflowEnabled() && - CRM_Core_Permission::check('create mailings')) - ); - - // Get all campaigns. - $allCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE); - $values = array(); - while ($dao->fetch()) { - $activityID = $dao->activity_id; - $values[$activityID]['activity_id'] = $dao->activity_id; - $values[$activityID]['source_record_id'] = $dao->source_record_id; - $values[$activityID]['activity_type_id'] = $dao->activity_type_id; - $values[$activityID]['activity_type'] = $dao->activity_type; - $values[$activityID]['activity_date_time'] = $dao->activity_date_time; - $values[$activityID]['status_id'] = $dao->status_id; - $values[$activityID]['subject'] = $dao->subject; - $values[$activityID]['campaign_id'] = $dao->campaign_id; - $values[$activityID]['is_recurring_activity'] = $dao->is_recurring_activity; - - if ($dao->campaign_id) { - $values[$activityID]['campaign'] = $allCampaigns[$dao->campaign_id]; - } - - if (empty($values[$activityID]['assignee_contact_name'])) { - $values[$activityID]['assignee_contact_name'] = array(); - } - - if (empty($values[$activityID]['target_contact_name'])) { - $values[$activityID]['target_contact_name'] = array(); - $values[$activityID]['target_contact_counter'] = $dao->counter; + // If no activity types are specified or excluded, return the list of all active ones + if (empty($params['activity_type_id']) && empty($params['activity_type_exclude_id'])) { + if (!empty($activityTypes)) { + return ['IN' => array_keys($activityTypes)]; } + return NULL; + } - // if deleted, wrap in - if ($dao->is_deleted) { - $dao->contact_name = "{$dao->contact_name}"; + // If we have specified activity types, build a list to return, excluding the ones we don't want. + if (!empty($params['activity_type_id'])) { + if (!is_array($params['activity_type_id'])) { + // Turn it into array if only one specified, so we don't duplicate processing below + $params['activity_type_id'] = [$params['activity_type_id'] => $params['activity_type_id']]; } - - if ($dao->record_type_id == $sourceID && $dao->contact_id) { - $values[$activityID]['source_contact_id'] = $dao->contact_id; - $values[$activityID]['source_contact_name'] = $dao->contact_name; + foreach ($params['activity_type_id'] as $value) { + // Add each activity type that was specified to list + $value = CRM_Utils_Type::escape($value, 'Positive'); + $activityTypes[$value] = $value; } + } - if (!$bulkActivityTypeID || ($bulkActivityTypeID != $dao->activity_type_id)) { - // build array of target / assignee names - if ($dao->record_type_id == $targetID && $dao->contact_id) { - $values[$activityID]['target_contact_name'][$dao->contact_id] = $dao->contact_name; - } - if ($dao->record_type_id == $assigneeID && $dao->contact_id) { - $values[$activityID]['assignee_contact_name'][$dao->contact_id] = $dao->contact_name; - } - - // case related fields - $values[$activityID]['case_id'] = $dao->case_id; - $values[$activityID]['case_subject'] = $dao->case_subject; - } - else { - $values[$activityID]['recipients'] = ts('(%1 recipients)', array(1 => $dao->counter)); - $values[$activityID]['mailingId'] = FALSE; - if ( - $accessCiviMail && - ($mailingIDs === TRUE || in_array($dao->source_record_id, $mailingIDs)) - ) { - $values[$activityID]['mailingId'] = TRUE; + // Build the list of activity types to exclude (from $params['activity_type_exclude_id']) + if (!empty($params['activity_type_exclude_id'])) { + if (!is_array($params['activity_type_exclude_id'])) { + // Turn it into array if only one specified, so we don't duplicate processing below + $params['activity_type_exclude_id'] = [$params['activity_type_exclude_id'] => $params['activity_type_exclude_id']]; + } + foreach ($params['activity_type_exclude_id'] as $value) { + // Remove each activity type from list if it should be excluded + $value = CRM_Utils_Type::escape($value, 'Positive'); + if (array_key_exists($value, $activityTypes)) { + unset($activityTypes[$value]); } } } - return $values; + return ['IN' => array_keys($activityTypes)]; } /** - * Get the component id and name if those are enabled and allowed. + * @inheritDoc + */ + public function addSelectWhereClause() { + $clauses = []; + // @todo - check if $permissedActivityTYpes === all activity types and do not add critieria if so. + $permittedActivityTypeIDs = self::getPermittedActivityTypes(); + if (empty($permittedActivityTypeIDs)) { + // This just prevents a mysql fail if they have no access - should be extremely edge case. + $permittedActivityTypeIDs = [0]; + } + $clauses['activity_type_id'] = ('IN (' . implode(', ', $permittedActivityTypeIDs) . ')'); + + $contactClause = CRM_Utils_SQL::mergeSubquery('Contact'); + if ($contactClause) { + $contactClause = implode(' AND contact_id ', $contactClause); + $clauses['id'][] = "IN (SELECT activity_id FROM civicrm_activity_contact WHERE contact_id $contactClause)"; + } + CRM_Utils_Hook::selectWhereClause($this, $clauses); + return $clauses; + } + + /** + * Get an array of components that are accessible by the currenct user. + * + * This means checking if they are enabled and if the user has appropriate permission. * - * Checks whether logged in user has permission. - * To decide whether we are going to include - * component related activities with core activity retrieve process. - * (what did that just mean?) + * For most components the permission is access component (e.g 'access CiviContribute'). + * Exceptions as CiviCampaign (administer CiviCampaign) and CiviCase + * (accesses a case function which enforces edit all cases or edit my cases. Case + * permissions are also handled on a per activity basis). + * + * Checks whether logged in user has permission to the component. + * + * @param bool $excludeComponentHandledActivities + * Should we exclude components whose display is handled in the components. + * In practice this means should we include CiviCase in the results. Presumbaly + * at the time it was decided case activities should be shown in the case framework and + * that this concept might be extended later. In practice most places that + * call this then re-add CiviCase in some way so it's all a bit... odd. * * @return array * Array of component id and name. */ - public static function activityComponents() { - $components = array(); + public static function activityComponents($excludeComponentHandledActivities = TRUE) { + $components = []; $compInfo = CRM_Core_Component::getEnabledComponents(); foreach ($compInfo as $compObj) { - if (!empty($compObj->info['showActivitiesInCore'])) { + $includeComponent = !$excludeComponentHandledActivities || !empty($compObj->info['showActivitiesInCore']); + if ($includeComponent) { if ($compObj->info['name'] == 'CiviCampaign') { $componentPermission = "administer {$compObj->name}"; } @@ -1161,220 +983,58 @@ public static function activityComponents() { * count of activities */ public static function getActivitiesCount($input) { - return self::getActivities($input, TRUE); + $activityParams = self::getActivityParamsForDashboardFunctions($input); + return civicrm_api3('Activity', 'getcount', $activityParams); } /** - * Get the activity Count. - * - * @param array $input - * Array of parameters. - * Keys include - * - contact_id int contact_id whose activities we want to retrieve - * - admin boolean if contact is admin - * - caseId int case ID - * - context string page on which selector is build - * - activity_type_id int|string the activity types we want to restrict by - * - * @return int - * count of activities - */ - public static function deprecatedGetActivitiesCount($input) { - $input['count'] = TRUE; - list($sqlClause, $params) = self::deprecatedGetActivitySQLClause($input); - - //filter case activities - CRM-5761 - $components = self::activityComponents(); - if (!in_array('CiviCase', $components)) { - $query = " - SELECT COUNT(DISTINCT(tbl.activity_id)) as count - FROM ( {$sqlClause} ) as tbl -LEFT JOIN civicrm_case_activity ON ( civicrm_case_activity.activity_id = tbl.activity_id ) - WHERE civicrm_case_activity.id IS NULL"; - } - else { - $query = "SELECT COUNT(DISTINCT(activity_id)) as count from ( {$sqlClause} ) as tbl"; - } - - return CRM_Core_DAO::singleValueQuery($query, $params); - } - - /** - * Get the activity sql clause to pick activities. - * - * @param array $input - * Array of parameters. - * Keys include - * - contact_id int contact_id whose activities we want to retrieve - * - admin boolean if contact is admin - * - caseId int case ID - * - context string page on which selector is build - * - count boolean are we interested in the count clause only? - * - activity_type_id int|string the activity types we want to restrict by + * @param int $userID + * @param string $subject + * @param string $html + * @param string $text + * @param string $additionalDetails + * @param int $campaignID + * @param array $attachments * * @return int - * count of activities + * The created activity ID + * @throws \CRM_Core_Exception */ - public static function deprecatedGetActivitySQLClause($input) { - $params = array(); - $sourceWhere = $targetWhere = $assigneeWhere = $caseWhere = 1; - - $config = CRM_Core_Config::singleton(); - if (!CRM_Utils_Array::value('admin', $input, FALSE)) { - $sourceWhere = ' ac.contact_id = %1 '; - $caseWhere = ' civicrm_case_contact.contact_id = %1 '; - - $params = array(1 => array($input['contact_id'], 'Integer')); - } + public static function createEmailActivity($userID, $subject, $html, $text, $additionalDetails, $campaignID, $attachments) { + $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email'); - $commonClauses = array( - "civicrm_option_group.name = 'activity_type'", - "civicrm_activity.is_deleted = 0", - "civicrm_activity.is_current_revision = 1", - "civicrm_activity.is_test= 0", - ); - - if ($input['context'] != 'activity') { - $commonClauses[] = "civicrm_activity.status_id = 1"; - } - - // Filter on component IDs. - $components = self::activityComponents(); - if (!empty($components)) { - $componentsIn = implode(',', array_keys($components)); - $commonClauses[] = "( civicrm_option_value.component_id IS NULL OR civicrm_option_value.component_id IN ( $componentsIn ) )"; + // CRM-6265: save both text and HTML parts in details (if present) + if ($html and $text) { + $details = "-ALTERNATIVE ITEM 0-\n$html$additionalDetails\n-ALTERNATIVE ITEM 1-\n$text$additionalDetails\n-ALTERNATIVE END-\n"; } else { - $commonClauses[] = "civicrm_option_value.component_id IS NULL"; - } - - // activity type ID clause - if (!empty($input['activity_type_id'])) { - if (is_array($input['activity_type_id'])) { - foreach ($input['activity_type_id'] as $idx => $value) { - $input['activity_type_id'][$idx] = CRM_Utils_Type::escape($value, 'Positive'); - } - $commonClauses[] = "civicrm_activity.activity_type_id IN ( " . implode(",", $input['activity_type_id']) . " ) "; - } - else { - $activityTypeID = CRM_Utils_Type::escape($input['activity_type_id'], 'Positive'); - $commonClauses[] = "civicrm_activity.activity_type_id = $activityTypeID"; - } + $details = $html ? $html : $text; + $details .= $additionalDetails; } - // exclude by activity type clause - if (!empty($input['activity_type_exclude_id'])) { - if (is_array($input['activity_type_exclude_id'])) { - foreach ($input['activity_type_exclude_id'] as $idx => $value) { - $input['activity_type_exclude_id'][$idx] = CRM_Utils_Type::escape($value, 'Positive'); - } - $commonClauses[] = "civicrm_activity.activity_type_id NOT IN ( " . implode(",", $input['activity_type_exclude_id']) . " ) "; - } - else { - $activityTypeID = CRM_Utils_Type::escape($input['activity_type_exclude_id'], 'Positive'); - $commonClauses[] = "civicrm_activity.activity_type_id != $activityTypeID"; - } - } + $activityParams = [ + 'source_contact_id' => $userID, + 'activity_type_id' => $activityTypeID, + 'activity_date_time' => date('YmdHis'), + 'subject' => $subject, + 'details' => $details, + // FIXME: check for name Completed and get ID from that lookup + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'), + 'campaign_id' => $campaignID, + ]; - $commonClause = implode(' AND ', $commonClauses); + // CRM-5916: strip [case #…] before saving the activity (if present in subject) + $activityParams['subject'] = preg_replace('/\[case #([0-9a-h]{7})\] /', '', $activityParams['subject']); - $includeCaseActivities = FALSE; - if (in_array('CiviCase', $components)) { - $includeCaseActivities = TRUE; + // add the attachments to activity params here + if ($attachments) { + // first process them + $activityParams = array_merge($activityParams, $attachments); } - // build main activity table select clause - $sourceSelect = ''; - - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); - $sourceJoin = " -INNER JOIN civicrm_activity_contact ac ON ac.activity_id = civicrm_activity.id -INNER JOIN civicrm_contact contact ON ac.contact_id = contact.id -"; - - if (!$input['count']) { - $sourceSelect = ', - civicrm_activity.activity_date_time, - civicrm_activity.source_record_id, - civicrm_activity.status_id, - civicrm_activity.subject, - contact.sort_name as source_contact_name, - civicrm_option_value.value as activity_type_id, - civicrm_option_value.label as activity_type, - null as case_id, null as case_subject, - civicrm_activity.campaign_id as campaign_id - '; - - $sourceJoin .= " -LEFT JOIN civicrm_activity_contact src ON (src.activity_id = ac.activity_id AND src.record_type_id = {$sourceID} AND src.contact_id = contact.id) -"; - } + $activity = self::create($activityParams); - $sourceClause = " - SELECT civicrm_activity.id as activity_id - {$sourceSelect} - from civicrm_activity - left join civicrm_option_value on - civicrm_activity.activity_type_id = civicrm_option_value.value - left join civicrm_option_group on - civicrm_option_group.id = civicrm_option_value.option_group_id - {$sourceJoin} - where - {$sourceWhere} - AND $commonClause - "; - - // Build case clause - // or else exclude Inbound Emails that have been filed on a case. - $caseClause = ''; - - if ($includeCaseActivities) { - $caseSelect = ''; - if (!$input['count']) { - $caseSelect = ', - civicrm_activity.activity_date_time, - civicrm_activity.source_record_id, - civicrm_activity.status_id, - civicrm_activity.subject, - contact.sort_name as source_contact_name, - civicrm_option_value.value as activity_type_id, - civicrm_option_value.label as activity_type, - null as case_id, null as case_subject, - civicrm_activity.campaign_id as campaign_id'; - } - - $caseClause = " - union all - - SELECT civicrm_activity.id as activity_id - {$caseSelect} - from civicrm_activity - inner join civicrm_case_activity on - civicrm_case_activity.activity_id = civicrm_activity.id - inner join civicrm_case on - civicrm_case_activity.case_id = civicrm_case.id - inner join civicrm_case_contact on - civicrm_case_contact.case_id = civicrm_case.id and {$caseWhere} - left join civicrm_option_value on - civicrm_activity.activity_type_id = civicrm_option_value.value - left join civicrm_option_group on - civicrm_option_group.id = civicrm_option_value.option_group_id - {$sourceJoin} - where - {$caseWhere} - AND $commonClause - and ( ( civicrm_case_activity.case_id IS NULL ) OR - ( civicrm_option_value.name <> 'Inbound Email' AND - civicrm_option_value.name <> 'Email' AND civicrm_case_activity.case_id - IS NOT NULL ) - ) - "; - } - - $returnClause = " {$sourceClause} {$caseClause} "; - - return array($returnClause, $params); + return $activity->id; } /** @@ -1408,6 +1068,8 @@ public static function deprecatedGetActivitySQLClause($input) { * * @return array * ( sent, activityId) if any email is sent and activityId + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public static function sendEmail( &$contactDetails, @@ -1432,7 +1094,7 @@ public static function sendEmail( list($fromDisplayName, $fromEmail, $fromDoNotEmail) = CRM_Contact_BAO_Contact::getContactDetails($userID); if (!$fromEmail) { - return array(count($contactDetails), 0, count($contactDetails)); + return [count($contactDetails), 0, count($contactDetails)]; } if (!trim($fromDisplayName)) { $fromDisplayName = $fromEmail; @@ -1451,48 +1113,9 @@ public static function sendEmail( } //create the meta level record first ( email activity ) - $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', - 'Email' - ); - - // CRM-6265: save both text and HTML parts in details (if present) - if ($html and $text) { - $details = "-ALTERNATIVE ITEM 0-\n$html$additionalDetails\n-ALTERNATIVE ITEM 1-\n$text$additionalDetails\n-ALTERNATIVE END-\n"; - } - else { - $details = $html ? $html : $text; - $details .= $additionalDetails; - } - - $activityParams = array( - 'source_contact_id' => $userID, - 'activity_type_id' => $activityTypeID, - 'activity_date_time' => date('YmdHis'), - 'subject' => $subject, - 'details' => $details, - // FIXME: check for name Completed and get ID from that lookup - 'status_id' => 2, - 'campaign_id' => $campaignId, - ); - - // CRM-5916: strip [case #…] before saving the activity (if present in subject) - $activityParams['subject'] = preg_replace('/\[case #([0-9a-h]{7})\] /', '', $activityParams['subject']); - - // add the attachments to activity params here - if ($attachments) { - // first process them - $activityParams = array_merge($activityParams, - $attachments - ); - } - - $activity = self::create($activityParams); + $activityID = self::createEmailActivity($userID, $subject, $html, $text, $additionalDetails, $campaignId, $attachments); - // get the set of attachments from where they are stored - $attachments = CRM_Core_BAO_File::getEntityFile('civicrm_activity', - $activity->id - ); - $returnProperties = array(); + $returnProperties = []; if (isset($messageToken['contact'])) { foreach ($messageToken['contact'] as $key => $value) { $returnProperties[$value] = 1; @@ -1508,7 +1131,7 @@ public static function sendEmail( } // get token details for contacts, call only if tokens are used - $details = array(); + $details = []; if (!empty($returnProperties) || !empty($tokens) || !empty($allTokens)) { list($details) = CRM_Utils_Token::getTokenDetails( $contactIds, @@ -1520,7 +1143,7 @@ public static function sendEmail( } // call token hook - $tokens = array(); + $tokens = []; CRM_Utils_Hook::tokens($tokens); $categories = array_keys($tokens); @@ -1530,7 +1153,7 @@ public static function sendEmail( $escapeSmarty = TRUE; } - $contributionDetails = array(); + $contributionDetails = []; if (!empty($contributionIds)) { $contributionDetails = CRM_Contribute_BAO_Contribution::replaceContributionTokens( $contributionIds, @@ -1543,7 +1166,7 @@ public static function sendEmail( ); } - $sent = $notSent = array(); + $sent = $notSent = []; foreach ($contactDetails as $values) { $contactId = $values['contact_id']; $emailAddress = $values['email']; @@ -1599,8 +1222,9 @@ public static function sendEmail( $tokenText, $tokenHtml, $emailAddress, - $activity->id, - $attachments, + $activityID, + // get the set of attachments from where they are stored + CRM_Core_BAO_File::getEntityFile('civicrm_activity', $activityID), $cc, $bcc ) @@ -1609,78 +1233,89 @@ public static function sendEmail( } } - return array($sent, $activity->id); + return [$sent, $activityID]; } /** - * Send SMS. + * Send SMS. Returns: bool $sent, int $activityId, int $success (number of sent SMS) * * @param array $contactDetails * @param array $activityParams - * @param array $smsParams - * @param $contactIds - * @param int $userID + * @param array $smsProviderParams + * @param array $contactIds + * @param int $sourceContactId This is the source contact Id * - * @return array + * @return array(bool $sent, int $activityId, int $success) * @throws CRM_Core_Exception */ public static function sendSMS( - &$contactDetails, + &$contactDetails = NULL, &$activityParams, - &$smsParams = array(), - &$contactIds, - $userID = NULL + &$smsProviderParams = [], + &$contactIds = NULL, + $sourceContactId = NULL ) { - if ($userID == NULL) { - $userID = CRM_Core_Session::getLoggedInContactID(); + if (!CRM_Core_Permission::check('send SMS')) { + throw new CRM_Core_Exception("You do not have the 'send SMS' permission"); } - $text = &$activityParams['sms_text_message']; - - // CRM-4575 - // token replacement of addressee/email/postal greetings - // get the tokens added in subject and message - $messageToken = CRM_Utils_Token::getTokens($text); + if (!isset($contactDetails) && !isset($contactIds)) { + throw new CRM_Core_Exception('You must specify either $contactDetails or $contactIds'); + } + // Populate $contactDetails and $contactIds if only one is set + if (is_array($contactIds) && !empty($contactIds) && empty($contactDetails)) { + foreach ($contactIds as $id) { + try { + $contactDetails[] = civicrm_api3('Contact', 'getsingle', ['contact_id' => $id]); + } + catch (Exception $e) { + // Contact Id doesn't exist + } + } + } + elseif (is_array($contactDetails) && !empty($contactDetails) && empty($contactIds)) { + foreach ($contactDetails as $contact) { + $contactIds[] = $contact['contact_id']; + } + } - // Create the meta level record first ( sms activity ) - $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', - 'activity_type_id', - 'SMS' - ); + // Get logged in User Id + if (empty($sourceContactId)) { + $sourceContactId = CRM_Core_Session::getLoggedInContactID(); + } - $details = $text; + $text = &$activityParams['sms_text_message']; - $activitySubject = $activityParams['activity_subject']; - $activityParams = array( - 'source_contact_id' => $userID, - 'activity_type_id' => $activityTypeID, + // Create the meta level record first ( sms activity ) + $activityParams = [ + 'source_contact_id' => $sourceContactId, + 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS'), 'activity_date_time' => date('YmdHis'), - 'subject' => $activitySubject, - 'details' => $details, - // FIXME: check for name Completed and get ID from that lookup - 'status_id' => 2, - ); - + 'subject' => $activityParams['activity_subject'], + 'details' => $text, + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'), + ]; $activity = self::create($activityParams); $activityID = $activity->id; - $returnProperties = array(); - + // Process Tokens + // token replacement of addressee/email/postal greetings + // get the tokens added in subject and message + $messageToken = CRM_Utils_Token::getTokens($text); + $returnProperties = []; if (isset($messageToken['contact'])) { foreach ($messageToken['contact'] as $key => $value) { $returnProperties[$value] = 1; } } - - // call token hook - $tokens = array(); + // Call tokens hook + $tokens = []; CRM_Utils_Hook::tokens($tokens); $categories = array_keys($tokens); - // get token details for contacts, call only if tokens are used - $details = array(); + $tokenDetails = []; if (!empty($returnProperties) || !empty($tokens)) { - list($details) = CRM_Utils_Token::getTokenDetails($contactIds, + list($tokenDetails) = CRM_Utils_Token::getTokenDetails($contactIds, $returnProperties, NULL, NULL, FALSE, $messageToken, @@ -1689,44 +1324,49 @@ public static function sendSMS( } $success = 0; - $escapeSmarty = FALSE; - $errMsgs = array(); - foreach ($contactDetails as $values) { - $contactId = $values['contact_id']; + $errMsgs = []; + foreach ($contactDetails as $contact) { + $contactId = $contact['contact_id']; - if (!empty($details) && is_array($details["{$contactId}"])) { + // Replace tokens + if (!empty($tokenDetails) && is_array($tokenDetails["{$contactId}"])) { // unset phone from details since it always returns primary number - unset($details["{$contactId}"]['phone']); - unset($details["{$contactId}"]['phone_type_id']); - $values = array_merge($values, $details["{$contactId}"]); + unset($tokenDetails["{$contactId}"]['phone']); + unset($tokenDetails["{$contactId}"]['phone_type_id']); + $contact = array_merge($contact, $tokenDetails["{$contactId}"]); } - - $tokenText = CRM_Utils_Token::replaceContactTokens($text, $values, FALSE, $messageToken, FALSE, $escapeSmarty); - $tokenText = CRM_Utils_Token::replaceHookTokens($tokenText, $values, $categories, FALSE, $escapeSmarty); + $tokenText = CRM_Utils_Token::replaceContactTokens($text, $contact, FALSE, $messageToken, FALSE, FALSE); + $tokenText = CRM_Utils_Token::replaceHookTokens($tokenText, $contact, $categories, FALSE, FALSE); // Only send if the phone is of type mobile - $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name'); - if ($values['phone_type_id'] == CRM_Utils_Array::value('Mobile', $phoneTypes)) { - $smsParams['To'] = $values['phone']; + if ($contact['phone_type_id'] == CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile')) { + $smsProviderParams['To'] = $contact['phone']; } else { - $smsParams['To'] = ''; + $smsProviderParams['To'] = ''; } - $sendResult = self::sendSMSMessage( - $contactId, - $tokenText, - $smsParams, - $activityID, - $userID - ); + $doNotSms = CRM_Utils_Array::value('do_not_sms', $contact, 0); - if (PEAR::isError($sendResult)) { - // Collect all of the PEAR_Error objects - $errMsgs[] = $sendResult; + if ($doNotSms) { + $errMsgs[] = PEAR::raiseError('Contact Does not accept SMS', NULL, PEAR_ERROR_RETURN); } else { - $success++; + $sendResult = self::sendSMSMessage( + $contactId, + $tokenText, + $smsProviderParams, + $activityID, + $sourceContactId + ); + + if (PEAR::isError($sendResult)) { + // Collect all of the PEAR_Error objects + $errMsgs[] = $sendResult; + } + else { + $success++; + } } } @@ -1742,7 +1382,7 @@ public static function sendSMS( $sent = $errMsgs; } - return array($sent, $activity->id, $success); + return [$sent, $activity->id, $success]; } /** @@ -1751,11 +1391,11 @@ public static function sendSMS( * @param int $toID * The contact id of the recipient. * @param $tokenText - * @param array $smsParams + * @param array $smsProviderParams * The params used for sending sms. * @param int $activityID * The activity ID that tracks the message. - * @param int $userID + * @param int $sourceContactID * * @return bool|PEAR_Error * true on success or PEAR_Error object @@ -1763,31 +1403,30 @@ public static function sendSMS( public static function sendSMSMessage( $toID, &$tokenText, - $smsParams = array(), + $smsProviderParams = [], $activityID, - $userID = NULL + $sourceContactID = NULL ) { - $toDoNotSms = ""; - $toPhoneNumber = ""; - - if ($smsParams['To']) { - $toPhoneNumber = trim($smsParams['To']); + $toPhoneNumber = NULL; + if ($smsProviderParams['To']) { + // If phone number is specified use it + $toPhoneNumber = trim($smsProviderParams['To']); } elseif ($toID) { - $filters = array('is_deceased' => 0, 'is_deleted' => 0, 'do_not_sms' => 0); + // No phone number specified, so find a suitable one for the contact + $filters = ['is_deceased' => 0, 'is_deleted' => 0, 'do_not_sms' => 0]; $toPhoneNumbers = CRM_Core_BAO_Phone::allPhones($toID, FALSE, 'Mobile', $filters); - // To get primary mobile phonenumber,if not get the first mobile phonenumber + // To get primary mobile phonenumber, if not get the first mobile phonenumber if (!empty($toPhoneNumbers)) { - $toPhoneNumerDetails = reset($toPhoneNumbers); - $toPhoneNumber = CRM_Utils_Array::value('phone', $toPhoneNumerDetails); + $toPhoneNumberDetails = reset($toPhoneNumbers); + $toPhoneNumber = CRM_Utils_Array::value('phone', $toPhoneNumberDetails); // Contact allows to send sms - $toDoNotSms = 0; } } // make sure both phone are valid // and that the recipient wants to receive sms - if (empty($toPhoneNumber) or $toDoNotSms) { + if (empty($toPhoneNumber)) { return PEAR::raiseError( 'Recipient phone number is invalid or recipient does not want to receive SMS', NULL, @@ -1795,25 +1434,23 @@ public static function sendSMSMessage( ); } - $recipient = $smsParams['To']; - $smsParams['contact_id'] = $toID; - $smsParams['parent_activity_id'] = $activityID; + $recipient = $toPhoneNumber; + $smsProviderParams['contact_id'] = $toID; + $smsProviderParams['parent_activity_id'] = $activityID; - $providerObj = CRM_SMS_Provider::singleton(array('provider_id' => $smsParams['provider_id'])); - $sendResult = $providerObj->send($recipient, $smsParams, $tokenText, NULL, $userID); + $providerObj = CRM_SMS_Provider::singleton(['provider_id' => $smsProviderParams['provider_id']]); + $sendResult = $providerObj->send($recipient, $smsProviderParams, $tokenText, NULL, $sourceContactID); if (PEAR::isError($sendResult)) { return $sendResult; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); - - // add activity target record for every sms that is send - $activityTargetParams = array( + // add activity target record for every sms that is sent + $targetID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'); + $activityTargetParams = [ 'activity_id' => $activityID, 'contact_id' => $toID, 'record_type_id' => $targetID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($activityTargetParams); return TRUE; @@ -1869,11 +1506,11 @@ public static function sendMessage( $toDisplayName = $toEmail; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); // create the params array - $mailParams = array( + $mailParams = [ 'groupName' => 'Activity Email Sender', 'from' => $from, 'toName' => $toDisplayName, @@ -1884,18 +1521,18 @@ public static function sendMessage( 'text' => $text_message, 'html' => $html_message, 'attachments' => $attachments, - ); + ]; if (!CRM_Utils_Mail::send($mailParams)) { return FALSE; } // add activity target record for every mail that is send - $activityTargetParams = array( + $activityTargetParams = [ 'activity_id' => $activityID, 'contact_id' => $toID, 'record_type_id' => $targetID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($activityTargetParams); return TRUE; } @@ -1915,26 +1552,26 @@ public static function sendMessage( public static function &importableFields($status = FALSE) { if (!self::$_importableFields) { if (!self::$_importableFields) { - self::$_importableFields = array(); + self::$_importableFields = []; } if (!$status) { - $fields = array('' => array('title' => ts('- do not import -'))); + $fields = ['' => ['title' => ts('- do not import -')]]; } else { - $fields = array('' => array('title' => ts('- Activity Fields -'))); + $fields = ['' => ['title' => ts('- Activity Fields -')]]; } $tmpFields = CRM_Activity_DAO_Activity::import(); $contactFields = CRM_Contact_BAO_Contact::importableFields('Individual', NULL); // Using new Dedupe rule. - $ruleParams = array( + $ruleParams = [ 'contact_type' => 'Individual', 'used' => 'Unsupervised', - ); + ]; $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams); - $tmpConatctField = array(); + $tmpConatctField = []; if (is_array($fieldsArray)) { foreach ($fieldsArray as $value) { $customFieldId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', @@ -1970,8 +1607,8 @@ public static function &importableFields($status = FALSE) { */ public static function getContactActivity($contactId) { // @todo remove this function entirely. - $activities = array(); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activities = []; + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -1998,7 +1635,7 @@ public static function getContactActivity($contactId) { $activityIds = array_keys($activities); if (count($activityIds) < 1) { - return array(); + return []; } $activityIds = implode(',', $activityIds); @@ -2011,9 +1648,6 @@ public static function getContactActivity($contactId) { $dao = CRM_Core_DAO::executeQuery($query); - $activityTypes = CRM_Core_OptionGroup::values('activity_type'); - $activityStatuses = CRM_Core_OptionGroup::values('activity_status'); - while ($dao->fetch()) { $activities[$dao->activity_id]['id'] = $dao->activity_id; $activities[$dao->activity_id]['activity_type_id'] = $dao->activity_type_id; @@ -2022,8 +1656,8 @@ public static function getContactActivity($contactId) { $activities[$dao->activity_id]['activity_date_time'] = $dao->activity_date_time; $activities[$dao->activity_id]['details'] = $dao->details; $activities[$dao->activity_id]['status_id'] = $dao->status_id; - $activities[$dao->activity_id]['activity_name'] = $activityTypes[$dao->activity_type_id]; - $activities[$dao->activity_id]['status'] = $activityStatuses[$dao->status_id]; + $activities[$dao->activity_id]['activity_name'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $dao->activity_type_id); + $activities[$dao->activity_id]['status'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_status_id', $dao->status_id); // set to null if not set if (!isset($activities[$dao->activity_id]['source_contact_id'])) { @@ -2050,7 +1684,7 @@ public static function addActivity( &$activity, $activityType = 'Membership Signup', $targetContactID = NULL, - $params = array() + $params = [] ) { $date = date('YmdHis'); if ($activity->__table == 'civicrm_membership') { @@ -2064,17 +1698,18 @@ public static function addActivity( } elseif ($activity->__table == 'civicrm_contribution') { // create activity record only for Completed Contributions - if ($activity->contribution_status_id != 1) { + $contributionCompletedStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); + if ($activity->contribution_status_id != $contributionCompletedStatusId) { return NULL; } $activityType = $component = 'Contribution'; // retrieve existing activity based on source_record_id and activity_type if (empty($params['id'])) { - $params['id'] = CRM_Utils_Array::value('id', civicrm_api3('Activity', 'Get', array( + $params['id'] = CRM_Utils_Array::value('id', civicrm_api3('Activity', 'Get', [ 'source_record_id' => $activity->id, 'activity_type_id' => $activityType, - ))); + ])); } if (!empty($params['id'])) { // CRM-13237 : if activity record found, update it with campaign id of contribution @@ -2084,7 +1719,7 @@ public static function addActivity( $date = CRM_Utils_Date::isoToMysql($activity->receive_date); } - $activityParams = array( + $activityParams = [ 'source_contact_id' => $activity->contact_id, 'source_record_id' => $activity->id, 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', $activityType), @@ -2093,7 +1728,7 @@ public static function addActivity( 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), 'skipRecentView' => TRUE, 'campaign_id' => $activity->campaign_id, - ); + ]; $activityParams = array_merge($activityParams, $params); if (empty($activityParams['subject'])) { @@ -2107,6 +1742,7 @@ public static function addActivity( $id = CRM_Core_Session::getLoggedInContactID(); if ($id) { $activityParams['source_contact_id'] = $id; + $activityParams['target_contact_id'][] = $activity->contact_id; } // CRM-14945 @@ -2115,7 +1751,7 @@ public static function addActivity( } //CRM-4027 if ($targetContactID) { - $activityParams['target_contact_id'] = $targetContactID; + $activityParams['target_contact_id'][] = $targetContactID; } // @todo - use api - remove lots of wrangling above. Remove deprecated fatal & let form layer // deal with any exceptions. @@ -2139,6 +1775,10 @@ public static function getActivitySubject($entityObj) { $membershipType = CRM_Member_PseudoConstant::membershipType($entityObj->membership_type_id); $subject = $membershipType ? $membershipType : ts('Membership'); + if (is_array($subject)) { + $subject = implode(", ", $subject); + } + if (!CRM_Utils_System::isNull($entityObj->source)) { $subject .= " - {$entityObj->source}"; } @@ -2172,7 +1812,8 @@ public static function getActivitySubject($entityObj) { $subject .= " - {$entityObj->source}"; } - return $subject; + // Amount and source could exceed max length of subject column. + return CRM_Utils_String::ellipsify($subject, 255); } } @@ -2186,12 +1827,12 @@ public static function getActivitySubject($entityObj) { * Id of parent activity otherwise false. */ public static function getParentActivity($activityId) { - static $parentActivities = array(); + static $parentActivities = []; $activityId = CRM_Utils_Type::escape($activityId, 'Integer'); if (!array_key_exists($activityId, $parentActivities)) { - $parentActivities[$activityId] = array(); + $parentActivities[$activityId] = []; $parentId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, @@ -2214,12 +1855,12 @@ public static function getParentActivity($activityId) { * $params count of prior activities otherwise false. */ public static function getPriorCount($activityID) { - static $priorCounts = array(); + static $priorCounts = []; $activityID = CRM_Utils_Type::escape($activityID, 'Integer'); if (!array_key_exists($activityID, $priorCounts)) { - $priorCounts[$activityID] = array(); + $priorCounts[$activityID] = []; $originalID = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityID, 'original_id' @@ -2233,7 +1874,7 @@ public static function getPriorCount($activityID) { AND is_current_revision = 0 AND id < {$activityID} "; - $params = array(1 => array($originalID, 'Integer')); + $params = [1 => [$originalID, 'Integer']]; $count = CRM_Core_DAO::singleValueQuery($query, $params); } $priorCounts[$activityID] = $count ? $count : 0; @@ -2253,13 +1894,13 @@ public static function getPriorCount($activityID) { * prior activities info. */ public static function getPriorAcitivities($activityID, $onlyPriorRevisions = FALSE) { - static $priorActivities = array(); + static $priorActivities = []; $activityID = CRM_Utils_Type::escape($activityID, 'Integer'); $index = $activityID . '_' . (int) $onlyPriorRevisions; if (!array_key_exists($index, $priorActivities)) { - $priorActivities[$index] = array(); + $priorActivities[$index] = []; $originalID = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityID, @@ -2282,7 +1923,7 @@ public static function getPriorAcitivities($activityID, $onlyPriorRevisions = FA } $query .= " ORDER BY ca.id DESC"; - $params = array(1 => array($originalID, 'Integer')); + $params = [1 => [$originalID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { @@ -2290,7 +1931,6 @@ public static function getPriorAcitivities($activityID, $onlyPriorRevisions = FA $priorActivities[$index][$dao->activityID]['name'] = $dao->name; $priorActivities[$index][$dao->activityID]['date'] = $dao->date; } - $dao->free(); } } return $priorActivities[$index]; @@ -2306,12 +1946,12 @@ public static function getPriorAcitivities($activityID, $onlyPriorRevisions = FA * current activity id. */ public static function getLatestActivityId($activityID) { - static $latestActivityIds = array(); + static $latestActivityIds = []; $activityID = CRM_Utils_Type::escape($activityID, 'Integer'); if (!array_key_exists($activityID, $latestActivityIds)) { - $latestActivityIds[$activityID] = array(); + $latestActivityIds[$activityID] = []; $originalID = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityID, @@ -2320,7 +1960,7 @@ public static function getLatestActivityId($activityID) { if ($originalID) { $activityID = $originalID; } - $params = array(1 => array($activityID, 'Integer')); + $params = [1 => [$activityID, 'Integer']]; $query = "SELECT id from civicrm_activity where original_id = %1 and is_current_revision = 1"; $latestActivityIds[$activityID] = CRM_Core_DAO::singleValueQuery($query, $params); @@ -2343,10 +1983,10 @@ public static function createFollowupActivity($activityId, $params) { return NULL; } - $followupParams = array(); + $followupParams = []; $followupParams['parent_id'] = $activityId; $followupParams['source_contact_id'] = CRM_Core_Session::getLoggedInContactID(); - $followupParams['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Scheduled', 'name'); + $followupParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled'); $followupParams['activity_type_id'] = $params['followup_activity_type_id']; // Get Subject of Follow-up Activiity, CRM-4491 @@ -2358,9 +1998,7 @@ public static function createFollowupActivity($activityId, $params) { $followupParams['target_contact_id'] = $params['target_contact_id']; } - $followupParams['activity_date_time'] = CRM_Utils_Date::processDate($params['followup_date'], - $params['followup_date_time'] - ); + $followupParams['activity_date_time'] = $params['followup_date']; $followupActivity = self::create($followupParams); return $followupActivity; @@ -2417,6 +2055,45 @@ public static function restoreActivity(&$params) { return $result; } + /** + * Return list of activity statuses of a given type. + * + * Note: activity status options use the "grouping" field to distinguish status types. + * Types are defined in class constants INCOMPLETE, COMPLETED, CANCELLED + * + * @param int $type + * + * @return array + */ + public static function getStatusesByType($type) { + if (!isset(Civi::$statics[__CLASS__][__FUNCTION__])) { + $statuses = civicrm_api3('OptionValue', 'get', [ + 'option_group_id' => 'activity_status', + 'return' => ['value', 'name', 'filter'], + 'options' => ['limit' => 0], + ]); + Civi::$statics[__CLASS__][__FUNCTION__] = $statuses['values']; + } + $ret = []; + foreach (Civi::$statics[__CLASS__][__FUNCTION__] as $status) { + if ($status['filter'] == $type) { + $ret[$status['value']] = $status['name']; + } + } + return $ret; + } + + /** + * Check if activity is overdue. + * + * @param array $activity + * + * @return bool + */ + public static function isOverdue($activity) { + return array_key_exists($activity['status_id'], self::getStatusesByType(self::INCOMPLETE)) && CRM_Utils_Date::overdue($activity['activity_date_time']); + } + /** * Get the exportable fields for Activities. * @@ -2426,69 +2103,94 @@ public static function restoreActivity(&$params) { * @return array * array of exportable Fields */ - public static function &exportableFields($name = 'Activity') { - if (!isset(self::$_exportableFields[$name])) { - self::$_exportableFields[$name] = array(); - - // TODO: ideally we should retrieve all fields from xml, in this case since activity processing is done - // my case hence we have defined fields as case_* - if ($name == 'Activity') { - $exportableFields = CRM_Activity_DAO_Activity::export(); - $exportableFields['source_contact_id']['title'] = ts('Source Contact ID'); - $exportableFields['source_contact'] = array( - 'title' => ts('Source Contact'), + public static function exportableFields($name = 'Activity') { + self::$_exportableFields[$name] = []; + + // TODO: ideally we should retrieve all fields from xml, in this case since activity processing is done + // my case hence we have defined fields as case_* + if ($name == 'Activity') { + $exportableFields = CRM_Activity_DAO_Activity::export(); + $exportableFields['source_contact_id'] = [ + 'title' => ts('Source Contact ID'), + 'type' => CRM_Utils_Type::T_INT, + ]; + $exportableFields['source_contact'] = [ + 'title' => ts('Source Contact'), + 'type' => CRM_Utils_Type::T_STRING, + ]; + + $Activityfields = [ + 'activity_type' => [ + 'title' => ts('Activity Type'), + 'name' => 'activity_type', 'type' => CRM_Utils_Type::T_STRING, - ); - - $Activityfields = array( - 'activity_type' => array( - 'title' => ts('Activity Type'), - 'name' => 'activity_type', - 'type' => CRM_Utils_Type::T_STRING, - 'searchByLabel' => TRUE, - ), - 'activity_status' => array( - 'title' => ts('Activity Status'), - 'name' => 'activity_status', - 'type' => CRM_Utils_Type::T_STRING, - 'searchByLabel' => TRUE, - ), - 'activity_priority' => array( - 'title' => ts('Activity Priority'), - 'name' => 'activity_priority', - 'type' => CRM_Utils_Type::T_STRING, - 'searchByLabel' => TRUE, - ), - ); - $fields = array_merge($Activityfields, $exportableFields); - } - else { - // Set title to activity fields. - $fields = array( - 'case_activity_subject' => array('title' => ts('Activity Subject'), 'type' => CRM_Utils_Type::T_STRING), - 'case_source_contact_id' => array('title' => ts('Activity Reporter'), 'type' => CRM_Utils_Type::T_STRING), - 'case_recent_activity_date' => array('title' => ts('Activity Actual Date'), 'type' => CRM_Utils_Type::T_DATE), - 'case_scheduled_activity_date' => array( - 'title' => ts('Activity Scheduled Date'), - 'type' => CRM_Utils_Type::T_DATE, - ), - 'case_recent_activity_type' => array('title' => ts('Activity Type'), 'type' => CRM_Utils_Type::T_STRING), - 'case_activity_status' => array('title' => ts('Activity Status'), 'type' => CRM_Utils_Type::T_STRING), - 'case_activity_duration' => array('title' => ts('Activity Duration'), 'type' => CRM_Utils_Type::T_INT), - 'case_activity_medium_id' => array('title' => ts('Activity Medium'), 'type' => CRM_Utils_Type::T_INT), - 'case_activity_details' => array('title' => ts('Activity Details'), 'type' => CRM_Utils_Type::T_TEXT), - 'case_activity_is_auto' => array( - 'title' => ts('Activity Auto-generated?'), - 'type' => CRM_Utils_Type::T_BOOLEAN, - ), - ); - } - - // add custom data for case activities - $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Activity')); - - self::$_exportableFields[$name] = $fields; + 'searchByLabel' => TRUE, + ], + 'activity_status' => [ + 'title' => ts('Activity Status'), + 'name' => 'activity_status', + 'type' => CRM_Utils_Type::T_STRING, + 'searchByLabel' => TRUE, + ], + 'activity_priority' => [ + 'title' => ts('Activity Priority'), + 'name' => 'activity_priority', + 'type' => CRM_Utils_Type::T_STRING, + 'searchByLabel' => TRUE, + ], + ]; + $fields = array_merge($Activityfields, $exportableFields); } + else { + // Set title to activity fields. + $fields = [ + 'case_activity_subject' => [ + 'title' => ts('Activity Subject'), + 'type' => CRM_Utils_Type::T_STRING, + ], + 'case_source_contact_id' => [ + 'title' => ts('Activity Reporter'), + 'type' => CRM_Utils_Type::T_STRING, + ], + 'case_recent_activity_date' => [ + 'title' => ts('Activity Actual Date'), + 'type' => CRM_Utils_Type::T_DATE, + ], + 'case_scheduled_activity_date' => [ + 'title' => ts('Activity Scheduled Date'), + 'type' => CRM_Utils_Type::T_DATE, + ], + 'case_recent_activity_type' => [ + 'title' => ts('Activity Type'), + 'type' => CRM_Utils_Type::T_STRING, + ], + 'case_activity_status' => [ + 'title' => ts('Activity Status'), + 'type' => CRM_Utils_Type::T_STRING, + ], + 'case_activity_duration' => [ + 'title' => ts('Activity Duration'), + 'type' => CRM_Utils_Type::T_INT, + ], + 'case_activity_medium_id' => [ + 'title' => ts('Activity Medium'), + 'type' => CRM_Utils_Type::T_INT, + ], + 'case_activity_details' => [ + 'title' => ts('Activity Details'), + 'type' => CRM_Utils_Type::T_TEXT, + ], + 'case_activity_is_auto' => [ + 'title' => ts('Activity Auto-generated?'), + 'type' => CRM_Utils_Type::T_BOOLEAN, + ], + ]; + } + + // add custom data for case activities + $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Activity')); + + self::$_exportableFields[$name] = $fields; return self::$_exportableFields[$name]; } @@ -2500,7 +2202,7 @@ public static function &exportableFields($name = 'Activity') { */ public static function getProfileFields() { $exportableFields = self::exportableFields('Activity'); - $skipFields = array( + $skipFields = [ 'activity_id', 'activity_type', 'source_contact_id', @@ -2509,7 +2211,7 @@ public static function getProfileFields() { 'activity_is_test', 'is_current_revision', 'activity_is_deleted', - ); + ]; $config = CRM_Core_Config::singleton(); if (!in_array('CiviCampaign', $config->enableComponents)) { $skipFields[] = 'activity_engagement_level'; @@ -2544,7 +2246,7 @@ public static function cleanupActivity($contactId) { if (!$contactId) { return $result; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $transaction = new CRM_Core_Transaction(); @@ -2565,14 +2267,12 @@ public static function cleanupActivity($contactId) { // delete activity only if no other contacts connected if (!$activityContactOther->find(TRUE)) { - $activityParams = array('id' => $activityContact->activity_id); + $activityParams = ['id' => $activityContact->activity_id]; $result = self::deleteActivity($activityParams); } - $activityContactOther->free(); } - $activityContact->free(); $transaction->commit(); return $result; @@ -2589,88 +2289,28 @@ public static function cleanupActivity($contactId) { * @return bool */ public static function checkPermission($activityId, $action) { - $allow = FALSE; + if (!$activityId || - !in_array($action, array(CRM_Core_Action::UPDATE, CRM_Core_Action::VIEW)) + !in_array($action, [CRM_Core_Action::UPDATE, CRM_Core_Action::VIEW]) ) { - return $allow; + return FALSE; } $activity = new CRM_Activity_DAO_Activity(); $activity->id = $activityId; if (!$activity->find(TRUE)) { - return $allow; - } - - // Component related permissions. - $compPermissions = array( - 'CiviCase' => array( - 'administer CiviCase', - 'access my cases and activities', - 'access all cases and activities', - ), - 'CiviMail' => array('access CiviMail'), - 'CiviEvent' => array('access CiviEvent'), - 'CiviGrant' => array('access CiviGrant'), - 'CiviPledge' => array('access CiviPledge'), - 'CiviMember' => array('access CiviMember'), - 'CiviReport' => array('access CiviReport'), - 'CiviContribute' => array('access CiviContribute'), - 'CiviCampaign' => array('administer CiviCampaign'), - ); - - // Return early when it is case activity. - $isCaseActivity = CRM_Case_BAO_Case::isCaseActivity($activityId); - // Check for civicase related permission. - if ($isCaseActivity) { - $allow = FALSE; - foreach ($compPermissions['CiviCase'] as $per) { - if (CRM_Core_Permission::check($per)) { - $allow = TRUE; - break; - } - } - - // Check for case specific permissions. - if ($allow) { - $oper = 'view'; - if ($action == CRM_Core_Action::UPDATE) { - $oper = 'edit'; - } - $allow = CRM_Case_BAO_Case::checkPermission($activityId, - $oper, - $activity->activity_type_id - ); - } - - return $allow; + return FALSE; } - // First check the component permission. - $sql = " - SELECT component_id - FROM civicrm_option_value val -INNER JOIN civicrm_option_group grp ON ( grp.id = val.option_group_id AND grp.name = %1 ) - WHERE val.value = %2"; - $params = array( - 1 => array('activity_type', 'String'), - 2 => array($activity->activity_type_id, 'Integer'), - ); - $componentId = CRM_Core_DAO::singleValueQuery($sql, $params); - - if ($componentId) { - $componentName = CRM_Core_Component::getComponentName($componentId); - $compPermission = CRM_Utils_Array::value($componentName, $compPermissions); - - // Here we are interesting in any single permission. - if (is_array($compPermission)) { - foreach ($compPermission as $per) { - if (CRM_Core_Permission::check($per)) { - $allow = TRUE; - break; - } - } - } + if (!self::hasPermissionForActivityType($activity->activity_type_id)) { + // this check is redundant for api access / anything that calls the selectWhereClause + // to determine ACLs. + return FALSE; + } + // Return early when it is case activity. + // Check for CiviCase related permission. + if (CRM_Case_BAO_Case::isCaseActivity($activityId)) { + return self::isContactPermittedAccessToCaseActivity($activityId, $action, $activity->activity_type_id); } // Check for this permission related to contact. @@ -2679,55 +2319,169 @@ public static function checkPermission($activityId, $action) { $permission = CRM_Core_Permission::EDIT; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); // Check for source contact. - if (!$componentId || $allow) { - $sourceContactId = self::getActivityContact($activity->id, $sourceID); - // Account for possibility of activity not having a source contact (as it may have been deleted). - $allow = $sourceContactId ? CRM_Contact_BAO_Contact_Permission::allow($sourceContactId, $permission) : TRUE; + $sourceContactId = self::getActivityContact($activity->id, $sourceID); + // Account for possibility of activity not having a source contact (as it may have been deleted). + $allow = $sourceContactId ? CRM_Contact_BAO_Contact_Permission::allow($sourceContactId, $permission) : TRUE; + if (!$allow) { + return FALSE; } // Check for target and assignee contacts. - if ($allow) { - // First check for supper permission. - $supPermission = 'view all contacts'; - if ($action == CRM_Core_Action::UPDATE) { - $supPermission = 'edit all contacts'; - } - $allow = CRM_Core_Permission::check($supPermission); - - // User might have sufficient permission, through acls. - if (!$allow) { - $allow = TRUE; - // Get the target contacts. - $targetContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $targetID); - foreach ($targetContacts as $cnt => $contactId) { + // First check for supper permission. + $supPermission = 'view all contacts'; + if ($action == CRM_Core_Action::UPDATE) { + $supPermission = 'edit all contacts'; + } + $allow = CRM_Core_Permission::check($supPermission); + + // User might have sufficient permission, through acls. + if (!$allow) { + $allow = TRUE; + // Get the target contacts. + $targetContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $targetID); + foreach ($targetContacts as $cnt => $contactId) { + if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) { + $allow = FALSE; + break; + } + } + + // Get the assignee contacts. + if ($allow) { + $assigneeContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $assigneeID); + foreach ($assigneeContacts as $cnt => $contactId) { if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) { $allow = FALSE; break; } } - - // Get the assignee contacts. - if ($allow) { - $assigneeContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activity->id, $assigneeID); - foreach ($assigneeContacts as $cnt => $contactId) { - if (!CRM_Contact_BAO_Contact_Permission::allow($contactId, $permission)) { - $allow = FALSE; - break; - } - } - } } } return $allow; } + /** + * Check if the logged in user has permission for the given case activity. + * + * @param int $activityId + * @param int $action + * @param int $activityTypeID + * + * @return bool + */ + protected static function isContactPermittedAccessToCaseActivity($activityId, $action, $activityTypeID) { + $oper = 'view'; + if ($action == CRM_Core_Action::UPDATE) { + $oper = 'edit'; + } + $allow = CRM_Case_BAO_Case::checkPermission($activityId, + $oper, + $activityTypeID + ); + + return $allow; + } + + /** + * Check if the logged in user has permission to access the given activity type. + * + * @param int $activityTypeID + * + * @return bool + */ + protected static function hasPermissionForActivityType($activityTypeID) { + $permittedActivityTypes = self::getPermittedActivityTypes(); + return isset($permittedActivityTypes[$activityTypeID]); + } + + /** + * Get the activity types the user is permitted to access. + * + * The types are filtered by the components they have access to. ie. a user + * with access CiviContribute but not CiviMember will see contribution related + * activities and activities with no component (e.g meetings) but not member related ones. + * + * @return array + */ + protected static function getPermittedActivityTypes() { + $userID = (int) CRM_Core_Session::getLoggedInContactID(); + if (!isset(Civi::$statics[__CLASS__]['permitted_activity_types'][$userID])) { + $permittedActivityTypes = []; + $components = self::activityComponents(FALSE); + $componentClause = empty($components) ? '' : (' OR component_id IN (' . implode(', ', array_keys($components)) . ')'); + + $types = CRM_Core_DAO::executeQuery( + " + SELECT option_value.value activity_type_id + FROM civicrm_option_value option_value +INNER JOIN civicrm_option_group grp ON (grp.id = option_group_id AND grp.name = 'activity_type') + WHERE component_id IS NULL $componentClause")->fetchAll(); + foreach ($types as $type) { + $permittedActivityTypes[$type['activity_type_id']] = (int) $type['activity_type_id']; + } + Civi::$statics[__CLASS__]['permitted_activity_types'][$userID] = $permittedActivityTypes; + } + return Civi::$statics[__CLASS__]['permitted_activity_types'][$userID]; + } + + /** + * @param $params + * @return array + */ + protected static function getActivityParamsForDashboardFunctions($params) { + $activityParams = [ + 'is_deleted' => 0, + 'is_current_revision' => 1, + 'is_test' => 0, + 'contact_id' => CRM_Utils_Array::value('contact_id', $params), + 'activity_date_time' => CRM_Utils_Array::value('activity_date_time', $params), + 'check_permissions' => 1, + 'options' => [ + 'offset' => CRM_Utils_Array::value('offset', $params, 0), + ], + ]; + + if (!empty($params['activity_status_id'])) { + $activityParams['activity_status_id'] = ['IN' => explode(',', $params['activity_status_id'])]; + } + + $activityParams['activity_type_id'] = self::filterActivityTypes($params); + $enabledComponents = self::activityComponents(); + // @todo - should we move this to activity get api. + foreach ([ + 'case_id' => 'CiviCase', + 'campaign_id' => 'CiviCampaign', + ] as $attr => $component) { + if (!in_array($component, $enabledComponents)) { + $activityParams[$attr] = ['IS NULL' => 1]; + } + } + return $activityParams; + } + + /** + * Checks if user has permissions to edit inbound e-mails, either basic info + * or both basic information and content. + * + * @return bool + */ + public static function checkEditInboundEmailsPermissions() { + if (CRM_Core_Permission::check('edit inbound email basic information') + || CRM_Core_Permission::check('edit inbound email basic information and content') + ) { + return TRUE; + } + + return FALSE; + } + /** * Wrapper for ajax activity selector. * @@ -2745,25 +2499,26 @@ public static function getContactActivitySelector(&$params) { $params['caseId'] = NULL; $context = CRM_Utils_Array::value('context', $params); $showContactOverlay = !CRM_Utils_String::startsWith($context, "dashlet"); - $activityTypeInfo = civicrm_api3('OptionValue', 'get', array( + $activityTypeInfo = civicrm_api3('OptionValue', 'get', [ 'option_group_id' => "activity_type", - 'options' => array('limit' => 0), - )); - $activityIcons = array(); + 'options' => ['limit' => 0], + ]); + $activityIcons = []; foreach ($activityTypeInfo['values'] as $type) { if (!empty($type['icon'])) { $activityIcons[$type['value']] = $type['icon']; } } + CRM_Utils_Date::convertFormDateToApiFormat($params, 'activity_date_time'); // Get contact activities. - $activities = CRM_Activity_BAO_Activity::deprecatedGetActivities($params); + $activities = CRM_Activity_BAO_Activity::getActivities($params); // Add total. - $params['total'] = CRM_Activity_BAO_Activity::deprecatedGetActivitiesCount($params); + $params['total'] = CRM_Activity_BAO_Activity::getActivitiesCount($params); // Format params and add links. - $contactActivities = array(); + $contactActivities = []; if (!empty($activities)) { $activityStatus = CRM_Core_PseudoConstant::activityStatus(); @@ -2771,7 +2526,7 @@ public static function getContactActivitySelector(&$params) { // Check logged in user for permission. $page = new CRM_Core_Page(); CRM_Contact_Page_View::checkUserPermission($page, $params['contact_id']); - $permissions = array($page->_permission); + $permissions = [$page->_permission]; if (CRM_Core_Permission::check('delete activities')) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -2779,27 +2534,24 @@ public static function getContactActivitySelector(&$params) { $mask = CRM_Core_Action::mask($permissions); foreach ($activities as $activityId => $values) { - $activity = array(); + $activity = ['source_contact_name' => '', 'target_contact_name' => '']; $activity['DT_RowId'] = $activityId; // Add class to this row if overdue. $activity['DT_RowClass'] = "crm-entity status-id-{$values['status_id']}"; - if (CRM_Utils_Date::overdue(CRM_Utils_Array::value('activity_date_time', $values)) - && CRM_Utils_Array::value('status_id', $values) == 1 - ) { + if (self::isOverdue($values)) { $activity['DT_RowClass'] .= ' status-overdue'; } else { $activity['DT_RowClass'] .= ' status-ontime'; } - $activity['DT_RowAttr'] = array(); + $activity['DT_RowAttr'] = []; $activity['DT_RowAttr']['data-entity'] = 'activity'; $activity['DT_RowAttr']['data-id'] = $activityId; $activity['activity_type'] = (!empty($activityIcons[$values['activity_type_id']]) ? ' ' : '') . $values['activity_type']; $activity['subject'] = $values['subject']; - $activity['source_contact_name'] = ''; if ($params['contact_id'] == $values['source_contact_id']) { $activity['source_contact_name'] = $values['source_contact_name']; } @@ -2812,13 +2564,12 @@ public static function getContactActivitySelector(&$params) { $values['source_contact_id']); } $activity['source_contact_name'] = $srcTypeImage . CRM_Utils_System::href($values['source_contact_name'], - 'civicrm/contact/view', "reset=1&cid={$values['source_contact_id']}"); + 'civicrm/contact/view', "reset=1&cid={$values['source_contact_id']}"); } else { $activity['source_contact_name'] = 'n/a'; } - $activity['target_contact_name'] = ''; if (isset($values['mailingId']) && !empty($values['mailingId'])) { $activity['target_contact'] = CRM_Utils_System::href($values['recipients'], 'civicrm/mailing/report/event', @@ -2827,28 +2578,31 @@ public static function getContactActivitySelector(&$params) { elseif (!empty($values['recipients'])) { $activity['target_contact_name'] = $values['recipients']; } - elseif (isset($values['target_contact_counter']) && $values['target_contact_counter']) { + elseif (isset($values['target_contact_count']) && $values['target_contact_count']) { $activity['target_contact_name'] = ''; - foreach ($values['target_contact_name'] as $tcID => $tcName) { - $targetTypeImage = ""; - $targetLink = CRM_Utils_System::href($tcName, 'civicrm/contact/view', "reset=1&cid={$tcID}"); + $firstTargetName = reset($values['target_contact_name']); + $firstTargetContactID = key($values['target_contact_name']); + + // The first target may not be accessable to the logged in user dev/core#1052 + if ($firstTargetName) { + $targetLink = CRM_Utils_System::href($firstTargetName, 'civicrm/contact/view', "reset=1&cid={$firstTargetContactID}"); if ($showContactOverlay) { $targetTypeImage = CRM_Contact_BAO_Contact_Utils::getImage( - CRM_Contact_BAO_Contact::getContactType($tcID), + CRM_Contact_BAO_Contact::getContactType($firstTargetContactID), FALSE, - $tcID); + $firstTargetContactID); $activity['target_contact_name'] .= "
$targetTypeImage $targetLink"; } else { $activity['target_contact_name'] .= $targetLink; } - } - if ($extraCount = $values['target_contact_counter'] - 1) { - $activity['target_contact_name'] .= ";
" . "(" . ts('%1 more', array(1 => $extraCount)) . ")"; - } - if ($showContactOverlay) { - $activity['target_contact_name'] .= "
"; + if ($extraCount = $values['target_contact_count'] - 1) { + $activity['target_contact_name'] .= ";
" . "(" . ts('%1 more', [1 => $extraCount]) . ")"; + } + if ($showContactOverlay) { + $activity['target_contact_name'] .= " "; + } } } elseif (!$values['target_contact_name']) { @@ -2914,12 +2668,12 @@ public static function getContactActivitySelector(&$params) { $activity['links'] = CRM_Core_Action::formLink($actionLinks, $actionMask, - array( + [ 'id' => $values['activity_id'], 'cid' => $params['contact_id'], 'cxt' => $context, 'caseid' => CRM_Utils_Array::value('case_id', $values), - ), + ], ts('more'), FALSE, 'activity.tab.row', @@ -2935,7 +2689,7 @@ public static function getContactActivitySelector(&$params) { } } - $activitiesDT = array(); + $activitiesDT = []; $activitiesDT['data'] = $contactActivities; $activitiesDT['recordsTotal'] = $params['total']; $activitiesDT['recordsFiltered'] = $params['total']; @@ -2952,7 +2706,7 @@ public static function getContactActivitySelector(&$params) { */ public static function copyExtendedActivityData($params) { // attach custom data to the new activity - $customParams = $htmlType = array(); + $customParams = $htmlType = []; $customValues = CRM_Core_BAO_CustomValueTable::getEntityValues($params['activityID'], 'Activity'); if (!empty($customValues)) { @@ -2969,10 +2723,10 @@ public static function copyExtendedActivityData($params) { // CRM-10542 if (in_array($key, $htmlType)) { $fileValues = CRM_Core_BAO_File::path($value, $params['activityID']); - $customParams["custom_{$key}_-1"] = array( + $customParams["custom_{$key}_-1"] = [ 'name' => $fileValues[0], - 'path' => $fileValues[1], - ); + 'type' => $fileValues[1], + ]; } else { $customParams["custom_{$key}_-1"] = $value; @@ -3019,7 +2773,7 @@ public static function getActivityContact($activityId, $recordTypeID = NULL, $co public static function getSourceContactID($activityId) { static $sourceID = NULL; if (!$sourceID) { - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); } @@ -3036,7 +2790,7 @@ public static function getSourceContactID($activityId) { public function setApiFilter(&$params) { if (!empty($params['target_contact_id'])) { $this->selectAdd(); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $obj = new CRM_Activity_BAO_ActivityContact(); $params['return.target_contact_id'] = 1; @@ -3055,7 +2809,7 @@ public function setApiFilter(&$params) { * * @return bool */ - public static function sendToAssignee($activity, $mailToContacts, $params = array()) { + public static function sendToAssignee($activity, $mailToContacts, $params = []) { if (!CRM_Utils_Array::crmIsEmptyArray($mailToContacts)) { $clientID = CRM_Utils_Array::value('client_id', $params); $caseID = CRM_Utils_Array::value('case_id', $params); @@ -3071,4 +2825,14 @@ public static function sendToAssignee($activity, $mailToContacts, $params = arra return FALSE; } + /** + * @return array + */ + public static function getEntityRefFilters() { + return [ + ['key' => 'activity_type_id', 'value' => ts('Activity Type')], + ['key' => 'status_id', 'value' => ts('Activity Status')], + ]; + } + } diff --git a/CRM/Activity/BAO/ActivityAssignment.php b/CRM/Activity/BAO/ActivityAssignment.php index 5e4a46b8d2b9..87420a3dc79f 100644 --- a/CRM/Activity/BAO/ActivityAssignment.php +++ b/CRM/Activity/BAO/ActivityAssignment.php @@ -1,9 +1,9 @@ copyValues($params); @@ -71,12 +71,12 @@ public static function create(&$params) { * @return array */ public static function retrieveAssigneeIdsByActivityId($activity_id) { - $assigneeArray = array(); + $assigneeArray = []; if (!CRM_Utils_Rule::positiveInteger($activity_id)) { return $assigneeArray; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $sql = " @@ -87,7 +87,12 @@ public static function retrieveAssigneeIdsByActivityId($activity_id) { AND record_type_id = $assigneeID AND civicrm_contact.is_deleted = 0 "; - $assignment = CRM_Core_DAO::executeQuery($sql, array(1 => array($activity_id, 'Integer'))); + $assignment = CRM_Core_DAO::executeQuery($sql, [ + 1 => [ + $activity_id, + 'Integer', + ], + ]); while ($assignment->fetch()) { $assigneeArray[] = $assignment->contact_id; } @@ -108,11 +113,11 @@ public static function retrieveAssigneeIdsByActivityId($activity_id) { * @return array */ public static function getAssigneeNames($activityIDs, $isDisplayName = FALSE, $skipDetails = TRUE) { - $assigneeNames = array(); + $assigneeNames = []; if (empty($activityIDs)) { return $assigneeNames; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $whereClause = ""; diff --git a/CRM/Activity/BAO/ActivityContact.php b/CRM/Activity/BAO/ActivityContact.php index ee63f83e7482..7039fba2f164 100644 --- a/CRM/Activity/BAO/ActivityContact.php +++ b/CRM/Activity/BAO/ActivityContact.php @@ -1,9 +1,9 @@ array($activityID, 'Integer'), - 2 => array($recordTypeID, 'Integer'), - ); + $params = [ + 1 => [$activityID, 'Integer'], + 2 => [$recordTypeID, 'Integer'], + ]; $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { @@ -98,7 +98,7 @@ public static function getNames($activityID, $recordTypeID, $alsoIDs = FALSE) { $ids[] = $dao->id; } - return $alsoIDs ? array($names, $ids) : $names; + return $alsoIDs ? [$names, $ids] : $names; } /** @@ -110,7 +110,7 @@ public static function getNames($activityID, $recordTypeID, $alsoIDs = FALSE) { * @return mixed */ public static function retrieveContactIdsByActivityId($activityID, $recordTypeID) { - $activityContact = array(); + $activityContact = []; if (!CRM_Utils_Rule::positiveInteger($activityID) || !CRM_Utils_Rule::positiveInteger($recordTypeID) ) { @@ -124,10 +124,10 @@ public static function retrieveContactIdsByActivityId($activityID, $recordTypeID AND record_type_id = %2 AND civicrm_contact.is_deleted = 0 "; - $params = array( - 1 => array($activityID, 'Integer'), - 2 => array($recordTypeID, 'Integer'), - ); + $params = [ + 1 => [$activityID, 'Integer'], + 2 => [$recordTypeID, 'Integer'], + ]; $dao = CRM_Core_DAO::executeQuery($sql, $params); while ($dao->fetch()) { @@ -147,12 +147,12 @@ public static function retrieveContactIdsByActivityId($activityID, $recordTypeID * @see DB_DataObject::getLink() * * @return array|null - * array = if there are links defined for this table. - * empty array - if there is a links.ini file, but no links on this table - * null - if no links.ini exists for this database (hence try auto_links). + * array if there are links defined for this table. + * empty array - if there is a links.ini file, but no links on this table + * null - if no links.ini exists for this database (hence try auto_links). */ public function links() { - $link = array('activity_id' => 'civicrm_activity:id'); + $link = ['activity_id' => 'civicrm_activity:id']; return $link; } diff --git a/CRM/Activity/BAO/ActivityTarget.php b/CRM/Activity/BAO/ActivityTarget.php index 76acae7f344f..167857d1c33d 100644 --- a/CRM/Activity/BAO/ActivityTarget.php +++ b/CRM/Activity/BAO/ActivityTarget.php @@ -1,9 +1,9 @@ copyValues($params); @@ -69,12 +69,12 @@ public static function create(&$params) { * @return mixed */ public static function retrieveTargetIdsByActivityId($activity_id) { - $targetArray = array(); + $targetArray = []; if (!CRM_Utils_Rule::positiveInteger($activity_id)) { return $targetArray; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $sql = " @@ -85,7 +85,12 @@ public static function retrieveTargetIdsByActivityId($activity_id) { AND record_type_id = $targetID AND civicrm_contact.is_deleted = 0 "; - $target = CRM_Core_DAO::executeQuery($sql, array(1 => array($activity_id, 'Integer'))); + $target = CRM_Core_DAO::executeQuery($sql, [ + 1 => [ + $activity_id, + 'Integer', + ], + ]); while ($target->fetch()) { $targetArray[] = $target->contact_id; } @@ -100,12 +105,12 @@ public static function retrieveTargetIdsByActivityId($activity_id) { * @return array */ public static function getTargetNames($activityID) { - $targetNames = array(); + $targetNames = []; if (empty($activityID)) { return $targetNames; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $query = " @@ -116,7 +121,7 @@ public static function getTargetNames($activityID) { AND civicrm_activity_contact.record_type_id = $targetID AND contact_a.is_deleted = 0 "; - $queryParam = array(1 => array($activityID, 'Integer')); + $queryParam = [1 => [$activityID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $queryParam); while ($dao->fetch()) { diff --git a/CRM/Activity/BAO/ActivityType.php b/CRM/Activity/BAO/ActivityType.php new file mode 100644 index 000000000000..d26d1438316a --- /dev/null +++ b/CRM/Activity/BAO/ActivityType.php @@ -0,0 +1,100 @@ + NULL, + 'displayLabel' => NULL, + 'id' => NULL, + ]; + + /** + * Constructor + * + * @param $activity_type_id int This matches up to the option_value 'value' column in the database. + */ + public function __construct($activity_type_id) { + $this->setActivityType($activity_type_id); + } + + /** + * Get the key/value pair representing this activity type. + * + * @return array + * @see $this->_activityType + */ + public function getActivityType() { + return $this->_activityType; + } + + /** + * Look up the key/value pair representing this activity type from the id. + * Generally called from constructor. + * + * @param $activity_type_id int This matches up to the option_value 'value' column in the database. + */ + public function setActivityType($activity_type_id) { + if ($activity_type_id && is_numeric($activity_type_id)) { + + /* + * These are pulled from CRM_Activity_Form_Activity. + * To avoid unexpectedly changing things like introducing hidden + * business logic or changing permission checks I've kept it using + * the same function call. It may or may not be desired to have + * that but this at least doesn't introduce anything that wasn't + * there before. + */ + $machineNames = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $activity_type_id, 'name'); + $displayLabels = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $activity_type_id, 'label'); + + $this->_activityType = [ + 'machineName' => CRM_Utils_Array::value($activity_type_id, $machineNames), + 'displayLabel' => CRM_Utils_Array::value($activity_type_id, $displayLabels), + 'id' => $activity_type_id, + ]; + } + } + +} diff --git a/CRM/Activity/BAO/ICalendar.php b/CRM/Activity/BAO/ICalendar.php index a94cd18e2438..cb2cb014eff7 100644 --- a/CRM/Activity/BAO/ICalendar.php +++ b/CRM/Activity/BAO/ICalendar.php @@ -1,9 +1,9 @@ get('activity_assignee_notification_ics')) { - $config = &CRM_Core_Config::singleton(); - $this->icsfile = tempnam($config->customFileUploadDir, 'ics'); + $this->icsfile = tempnam(CRM_Core_Config::singleton()->customFileUploadDir, 'ics'); if ($this->icsfile !== FALSE) { rename($this->icsfile, $this->icsfile . '.ics'); $this->icsfile .= '.ics'; @@ -86,14 +85,14 @@ public function addAttachment(&$attachments, $contacts) { $calendar = $template->fetch('CRM/Activity/Calendar/ICal.tpl'); if (file_put_contents($this->icsfile, $calendar) !== FALSE) { if (empty($attachments)) { - $attachments = array(); + $attachments = []; } - $attachments['activity_ics'] = array( + $attachments['activity_ics'] = [ 'mime_type' => 'text/calendar', 'fileName' => $icsFileName, 'cleanName' => $icsFileName, 'fullPath' => $this->icsfile, - ); + ]; return 'activity_ics'; } } @@ -105,7 +104,7 @@ public function addAttachment(&$attachments, $contacts) { * Remove temp file. */ public function cleanup() { - if (!empty ($this->icsfile)) { + if (!empty($this->icsfile)) { @unlink($this->icsfile); } } diff --git a/CRM/Activity/BAO/Query.php b/CRM/Activity/BAO/Query.php index 315ef64ff4dd..6cea3e0f908c 100644 --- a/CRM/Activity/BAO/Query.php +++ b/CRM/Activity/BAO/Query.php @@ -1,9 +1,9 @@ _returnProperties['activity_status_id'])) { - $query->_select['activity_status_id'] = 'activity_status.value as activity_status_id'; + $query->_select['activity_status_id'] = 'civicrm_activity.status_id as activity_status_id'; $query->_element['activity_status_id'] = 1; $query->_tables['civicrm_activity'] = 1; - $query->_tables['activity_status'] = 1; $query->_whereTables['civicrm_activity'] = 1; - $query->_whereTables['activity_status'] = 1; } if (!empty($query->_returnProperties['activity_status'])) { @@ -138,7 +136,13 @@ public static function select(&$query) { if (!empty($query->_returnProperties['source_contact'])) { $query->_select['source_contact'] = 'source_contact.sort_name as source_contact'; $query->_element['source_contact'] = 1; - $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1; + $query->_tables['civicrm_activity'] = $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1; + } + + if (!empty($query->_returnProperties['source_contact_id'])) { + $query->_select['source_contact_id'] = 'source_contact.id as source_contact_id'; + $query->_element['source_contact_id'] = 1; + $query->_tables['civicrm_activity'] = $query->_tables['source_contact'] = $query->_whereTables['source_contact'] = 1; } if (!empty($query->_returnProperties['activity_result'])) { @@ -171,7 +175,7 @@ public static function select(&$query) { public static function where(&$query) { foreach (array_keys($query->_params) as $id) { if (substr($query->_params[$id][0], 0, 9) == 'activity_') { - if ($query->_mode == CRM_Contact_BAO_QUERY::MODE_CONTACTS) { + if ($query->_mode == CRM_Contact_BAO_Query::MODE_CONTACTS) { $query->_useDistinct = TRUE; } $query->_params[$id][3]; @@ -207,22 +211,30 @@ public static function whereClauseSingle(&$values, &$query) { case 'activity_subject': $qillName = $name; - if (in_array($name, array('activity_engagement_level', 'activity_id'))) { + if (in_array($name, ['activity_engagement_level', 'activity_id'])) { $name = $qillName = str_replace('activity_', '', $name); } - if (in_array($name, array('activity_status_id', 'activity_subject', 'activity_priority_id'))) { + if (in_array($name, [ + 'activity_status_id', + 'activity_subject', + 'activity_priority_id', + ])) { $name = str_replace('activity_', '', $name); $qillName = str_replace('_id', '', $qillName); } if ($name == 'activity_campaign_id') { - $name = 'campaign_id'; + $name = 'campaign_id'; } $dataType = !empty($fields[$qillName]['type']) ? CRM_Utils_Type::typeToString($fields[$qillName]['type']) : 'String'; $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_activity.$name", $op, $value, $dataType); list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Activity_DAO_Activity', $name, $value, $op); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$qillName]['title'], 2 => $op, 3 => $value)); + $query->_qill[$grouping][] = ts('%1 %2 %3', [ + 1 => $fields[$qillName]['title'], + 2 => $op, + 3 => $value, + ]); break; case 'activity_text': @@ -234,7 +246,11 @@ public static function whereClauseSingle(&$values, &$query) { case 'activity_priority': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("$name.label", $op, $value, 'String'); list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Activity_DAO_Activity', $name, $value, $op); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$name]['title'], 2 => $op, 3 => $value)); + $query->_qill[$grouping][] = ts('%1 %2 %3', [ + 1 => $fields[$name]['title'], + 2 => $op, + 3 => $value, + ]); $query->_tables[$name] = $query->_whereTables[$name] = 1; break; @@ -249,7 +265,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'activity_role': CRM_Contact_BAO_Query::$_activityRole = $values[2]; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -284,14 +300,19 @@ public static function whereClauseSingle(&$values, &$query) { case 'activity_date': case 'activity_date_low': case 'activity_date_high': + case 'activity_date_time_low': + case 'activity_date_time_high': $query->dateQueryBuilder($values, - 'civicrm_activity', 'activity_date', 'activity_date_time', ts('Activity Date') + 'civicrm_activity', str_replace([ + '_high', + '_low', + ], '', $name), 'activity_date_time', ts('Activity Date') ); break; case 'activity_taglist': $taglist = $value; - $value = array(); + $value = []; foreach ($taglist as $val) { if ($val) { $val = explode(',', $val); @@ -304,38 +325,40 @@ public static function whereClauseSingle(&$values, &$query) { } case 'activity_tags': - $value = array_keys($value); - $activityTags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $activityTags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); - $names = array(); - if (is_array($value)) { - foreach ($value as $k => $v) { - $names[] = $activityTags[$v]; - } + if (!is_array($value)) { + $value = explode(',', $value); + } + + $names = []; + foreach ($value as $k => $v) { + $names[] = $activityTags[$v]; } + $query->_where[$grouping][] = "civicrm_activity_tag.tag_id IN (" . implode(",", $value) . ")"; - $query->_qill[$grouping][] = ts('Activity Tag %1', array(1 => $op)) . ' ' . implode(' ' . ts('OR') . ' ', $names); + $query->_qill[$grouping][] = ts('Activity Tag %1', [1 => $op]) . ' ' . implode(' ' . ts('OR') . ' ', $names); $query->_tables['civicrm_activity_tag'] = $query->_whereTables['civicrm_activity_tag'] = 1; break; case 'activity_result': if (is_array($value)) { - $safe = NULL; - while (list(, $k) = each($value)) { + $safe = []; + foreach ($value as $id => $k) { $safe[] = "'" . CRM_Utils_Type::escape($k, 'String') . "'"; } $query->_where[$grouping][] = "civicrm_activity.result IN (" . implode(',', $safe) . ")"; - $query->_qill[$grouping][] = ts("Activity Result - %1", array(1 => implode(' or ', $safe))); + $query->_qill[$grouping][] = ts("Activity Result - %1", [1 => implode(' or ', $safe)]); } break; case 'parent_id': if ($value == 1) { - $query->_where[$grouping][] = "parent_id.parent_id IS NOT NULL"; + $query->_where[$grouping][] = "civicrm_activity.parent_id IS NOT NULL"; $query->_qill[$grouping][] = ts('Activities which have Followup Activities'); } elseif ($value == 2) { - $query->_where[$grouping][] = "parent_id.parent_id IS NULL"; + $query->_where[$grouping][] = "civicrm_activity.parent_id IS NULL"; $query->_qill[$grouping][] = ts('Activities without Followup Activities'); } break; @@ -350,6 +373,18 @@ public static function whereClauseSingle(&$values, &$query) { $query->_qill[$grouping][] = ts('Activities which are not Followup Activities'); } break; + + case 'source_contact': + case 'source_contact_id': + $columnName = strstr($name, '_id') ? 'id' : 'sort_name'; + $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("source_contact.{$columnName}", $op, $value, CRM_Utils_Type::typeToString($fields[$name]['type'])); + list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contact_DAO_Contact', $columnName, $value, $op); + $query->_qill[$grouping][] = ts('%1 %2 %3', [ + 1 => $fields[$name]['title'], + 2 => $op, + 3 => $value, + ]); + break; } } @@ -401,12 +436,16 @@ public static function from($name, $mode, $side) { break; case 'source_contact': - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); + $sourceID = CRM_Core_PseudoConstant::getKey( + 'CRM_Activity_BAO_ActivityContact', + 'record_type_id', + 'Activity Source' + ); $from = " - LEFT JOIN civicrm_activity_contact ac - ON ( ac.activity_id = civicrm_activity_contact.activity_id AND ac.record_type_id = {$sourceID}) - INNER JOIN civicrm_contact source_contact ON (ac.contact_id = source_contact.id)"; + LEFT JOIN civicrm_activity_contact source_activity + ON (source_activity.activity_id = civicrm_activity_contact.activity_id + AND source_activity.record_type_id = {$sourceID}) + LEFT JOIN civicrm_contact source_contact ON (source_activity.contact_id = source_contact.id)"; break; case 'parent_id': @@ -417,63 +456,84 @@ public static function from($name, $mode, $side) { return $from; } + /** + * Get the metadata for fields to be included on the activity search form. + * + * @todo ideally this would be a trait included on the activity search & advanced search + * rather than a static function. + */ + public static function getSearchFieldMetadata() { + $fields = ['activity_type_id', 'activity_date_time', 'priority_id', 'activity_location']; + $metadata = civicrm_api3('Activity', 'getfields', [])['values']; + $metadata = array_intersect_key($metadata, array_flip($fields)); + $metadata['activity_text'] = [ + 'title' => ts('Activity Text'), + 'type' => CRM_Utils_Type::T_STRING, + 'is_pseudofield' => TRUE, + ]; + return $metadata; + } + /** * Add all the elements shared between case activity search and advanced search. * - * @param CRM_Core_Form $form + * @param CRM_Core_Form_Search $form */ public static function buildSearchForm(&$form) { - $form->addSelect('activity_type_id', - array('entity' => 'activity', 'label' => ts('Activity Type(s)'), 'multiple' => 'multiple', 'option_url' => NULL, 'placeholder' => ts('- any -')) - ); - - CRM_Core_Form_Date::buildDateRange($form, 'activity_date', 1, '_low', '_high', ts('From'), FALSE, FALSE); - $form->addElement('hidden', 'activity_date_range_error'); - $form->addFormRule(array('CRM_Activity_BAO_Query', 'formRule'), $form); + $form->addSearchFieldMetadata(['Activity' => self::getSearchFieldMetadata()]); + $form->addFormFieldsFromMetadata(); - $followUpActivity = array( + $followUpActivity = [ 1 => ts('Yes'), 2 => ts('No'), - ); - $form->addRadio('parent_id', NULL, $followUpActivity, array('allowClear' => TRUE)); - $form->addRadio('followup_parent_id', NULL, $followUpActivity, array('allowClear' => TRUE)); - $activityRoles = array( + ]; + $form->addRadio('parent_id', NULL, $followUpActivity, ['allowClear' => TRUE]); + $form->addRadio('followup_parent_id', NULL, $followUpActivity, ['allowClear' => TRUE]); + $activityRoles = [ 3 => ts('With'), 2 => ts('Assigned to'), 1 => ts('Added by'), - ); - $form->addRadio('activity_role', NULL, $activityRoles, array('allowClear' => TRUE)); - $form->setDefaults(array('activity_role' => 3)); - $activityStatus = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'status_id', array('flip' => 1, 'labelColumn' => 'name')); + ]; + $form->addRadio('activity_role', NULL, $activityRoles, ['allowClear' => TRUE]); + $form->setDefaults(['activity_role' => 3]); + $activityStatus = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'status_id', [ + 'flip' => 1, + 'labelColumn' => 'name', + ]); $form->addSelect('status_id', - array('entity' => 'activity', 'multiple' => 'multiple', 'option_url' => NULL, 'placeholder' => ts('- any -')) + [ + 'entity' => 'activity', + 'multiple' => 'multiple', + 'option_url' => NULL, + 'placeholder' => ts('- any -'), + ] ); $ssID = $form->get('ssID'); - $status = array($activityStatus['Completed'], $activityStatus['Scheduled']); + $status = [$activityStatus['Completed'], $activityStatus['Scheduled']]; //If status is saved in smart group. if (!empty($ssID) && !empty($form->_formValues['activity_status_id'])) { $status = $form->_formValues['activity_status_id']; } - $form->setDefaults(array('status_id' => $status)); + $form->setDefaults(['status_id' => $status]); $form->addElement('text', 'activity_text', ts('Activity Text'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')); $form->addRadio('activity_option', '', CRM_Core_SelectValues::activityTextOptions()); - $form->setDefaults(array('activity_option' => 'both')); - - $priority = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id'); - $form->addSelect('priority_id', - array('entity' => 'activity', 'label' => ts('Priority'), 'multiple' => 'multiple', 'option_url' => NULL, 'placeholder' => ts('- any -')) - ); + $form->setDefaults(['activity_option' => 'both']); $form->addYesNo('activity_test', ts('Activity is a Test?')); - $activity_tags = CRM_Core_BAO_Tag::getTags('civicrm_activity'); + $activity_tags = CRM_Core_BAO_Tag::getColorTags('civicrm_activity'); + if ($activity_tags) { - foreach ($activity_tags as $tagID => $tagName) { - $form->_tagElement = &$form->addElement('checkbox', "activity_tags[$tagID]", - NULL, $tagName - ); - } + $form->add('select2', 'activity_tags', ts('Activity Tag(s)'), + $activity_tags, FALSE, [ + 'id' => 'activity_tags', + 'multiple' => + 'multiple', + 'class' => 'crm-select2', + 'placeholder' => ts('- select -'), + ] + ); } $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_activity'); @@ -482,12 +542,12 @@ public static function buildSearchForm(&$form) { $surveys = CRM_Campaign_BAO_Survey::getSurveys(TRUE, FALSE, FALSE, TRUE); if ($surveys) { $form->add('select', 'activity_survey_id', ts('Survey / Petition'), - array('' => ts('- none -')) + $surveys, FALSE, - array('class' => 'crm-select2') - ); + ['' => ts('- none -')] + $surveys, FALSE, + ['class' => 'crm-select2'] + ); } - CRM_Core_BAO_Query::addCustomFormFields($form, array('Activity')); + CRM_Core_BAO_Query::addCustomFormFields($form, ['Activity']); CRM_Campaign_BAO_Campaign::addCampaignInComponentSearch($form, 'activity_campaign_id'); @@ -498,16 +558,19 @@ public static function buildSearchForm(&$form) { CRM_Campaign_BAO_Campaign::accessCampaign() ) { $buildEngagementLevel = TRUE; - $form->addSelect('activity_engagement_level', array('entity' => 'activity', 'context' => 'search')); + $form->addSelect('activity_engagement_level', [ + 'entity' => 'activity', + 'context' => 'search', + ]); // Add survey result field. $optionGroups = CRM_Campaign_BAO_Survey::getResultSets('name'); - $resultOptions = array(); + $resultOptions = []; foreach ($optionGroups as $gid => $name) { if ($name) { $value = CRM_Core_OptionGroup::values($name); if (!empty($value)) { - while (list($k, $v) = each($value)) { + foreach ($value as $k => $v) { $resultOptions[$v] = $v; } } @@ -520,14 +583,18 @@ public static function buildSearchForm(&$form) { asort($resultOptions); $form->add('select', 'activity_result', ts("Survey Result"), $resultOptions, FALSE, - array('id' => 'activity_result', 'multiple' => 'multiple', 'class' => 'crm-select2') + [ + 'id' => 'activity_result', + 'multiple' => 'multiple', + 'class' => 'crm-select2', + ] ); } } $form->assign('buildEngagementLevel', $buildEngagementLevel); $form->assign('buildSurveyResult', $buildSurveyResult); - $form->setDefaults(array('activity_test' => 0)); + $form->setDefaults(['activity_test' => 0]); } /** @@ -539,7 +606,7 @@ public static function buildSearchForm(&$form) { public static function defaultReturnProperties($mode, $includeCustomFields = TRUE) { $properties = NULL; if ($mode & CRM_Contact_BAO_Query::MODE_ACTIVITY) { - $properties = array( + $properties = [ 'activity_id' => 1, 'contact_type' => 1, 'contact_sub_type' => 1, @@ -561,7 +628,7 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU 'result' => 1, 'activity_engagement_level' => 1, 'parent_id' => 1, - ); + ]; if ($includeCustomFields) { // also get all the custom activity properties @@ -578,30 +645,39 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU } /** - * Custom form rules. + * Get the list of fields required to populate the selector. * - * @param array $fields - * @param array $files - * @param CRM_Core_Form $form - * - * @return bool|array + * The default return properties array returns far too many fields for 'everyday use. Every field you add to this array + * kills a small kitten so add carefully. */ - public static function formRule($fields, $files, $form) { - $errors = array(); - - if (empty($fields['activity_date_low']) || empty($fields['activity_date_high'])) { - return TRUE; - } - - CRM_Utils_Rule::validDateRange($fields, 'activity_date', $errors, ts('Activity Date')); + public static function selectorReturnProperties() { + $properties = [ + 'activity_id' => 1, + 'contact_type' => 1, + 'contact_sub_type' => 1, + 'sort_name' => 1, + 'display_name' => 1, + 'activity_type_id' => 1, + 'activity_subject' => 1, + 'activity_date_time' => 1, + 'activity_status_id' => 1, + 'source_contact' => 1, + 'source_record_id' => 1, + 'activity_is_test' => 1, + 'activity_campaign_id' => 1, + 'activity_engagement_level' => 1, + ]; - return empty($errors) ? TRUE : $errors; + return $properties; } /** * Where/qill clause for notes * * @param array $values + * @param CRM_Contact_BAO_Query $query + * + * @throws \CRM_Core_Exception */ public static function whereClauseSingleActivityText(&$values, &$query) { list($name, $op, $value, $grouping, $wildcard) = $values; @@ -610,8 +686,8 @@ public static function whereClauseSingleActivityText(&$values, &$query) { $query->_useDistinct = TRUE; - $label = ts('Activity Text (%1)', array(1 => CRM_Utils_Array::value($activityOption, CRM_Core_SelectValues::activityTextOptions()))); - $clauses = array(); + $label = ts('Activity Text (%1)', [1 => CRM_Utils_Array::value($activityOption, CRM_Core_SelectValues::activityTextOptions())]); + $clauses = []; if ($activityOption % 2 == 0) { $clauses[] = $query->buildClause('civicrm_activity.details', $op, $value, 'String'); } @@ -621,7 +697,11 @@ public static function whereClauseSingleActivityText(&$values, &$query) { $query->_where[$grouping][] = "( " . implode(' OR ', $clauses) . " )"; list($qillOp, $qillVal) = $query->buildQillForFieldValue(NULL, $name, $value, $op); - $query->_qill[$grouping][] = ts("%1 %2 '%3'", array(1 => $label, 2 => $qillOp, 3 => $qillVal)); + $query->_qill[$grouping][] = ts("%1 %2 '%3'", [ + 1 => $label, + 2 => $qillOp, + 3 => $qillVal, + ]); } } diff --git a/CRM/Activity/Controller/Search.php b/CRM/Activity/Controller/Search.php index 1395d13f62ad..67f358a50d86 100644 --- a/CRM/Activity/Controller/Search.php +++ b/CRM/Activity/Controller/Search.php @@ -1,9 +1,9 @@ __table = 'civicrm_activity'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'phone_id', 'civicrm_phone', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'parent_id', 'civicrm_activity', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'relationship_id', 'civicrm_relationship', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'original_id', 'civicrm_activity', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'phone_id', 'civicrm_phone', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'parent_id', 'civicrm_activity', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'relationship_id', 'civicrm_relationship', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'original_id', 'civicrm_activity', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'activity_id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'activity_id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity ID') , - 'description' => 'Unique Other Activity ID', - 'required' => true, - 'import' => true, + 'title' => ts('Activity ID'), + 'description' => ts('Unique Other Activity ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_activity.id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - ) , - 'source_record_id' => array( + ], + 'source_record_id' => [ 'name' => 'source_record_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Source Record') , - 'description' => 'Artificial FK to original transaction (e.g. contribution) IF it is not an Activity. Table can be figured out through activity_type_id, and further through component registry.', + 'title' => ts('Source Record'), + 'description' => ts('Artificial FK to original transaction (e.g. contribution) IF it is not an Activity. Table can be figured out through activity_type_id, and further through component registry.'), + 'where' => 'civicrm_activity.source_record_id', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - ) , - 'activity_type_id' => array( + ], + 'activity_type_id' => [ 'name' => 'activity_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity Type ID') , - 'description' => 'FK to civicrm_option_value.id, that has to be valid, registered activity type.', - 'required' => true, - 'import' => true, + 'title' => ts('Activity Type ID'), + 'description' => ts('FK to civicrm_option_value.id, that has to be valid, registered activity type.'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_activity.activity_type_id', 'headerPattern' => '/(activity.)?type(.id$)/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'activity_type', 'optionEditPath' => 'civicrm/admin/options/activity_type', - ) - ) , - 'activity_subject' => array( + ], + ], + 'activity_subject' => [ 'name' => 'subject', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Subject') , - 'description' => 'The subject/purpose/short description of the activity.', + 'title' => ts('Subject'), + 'description' => ts('The subject/purpose/short description of the activity.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_activity.subject', 'headerPattern' => '/(activity.)?subject/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'activity_date_time' => array( + ], + ], + 'activity_date_time' => [ 'name' => 'activity_date_time', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Activity Date') , - 'description' => 'Date and time this activity is scheduled to occur. Formerly named scheduled_date_time.', - 'import' => true, + 'title' => ts('Activity Date'), + 'description' => ts('Date and time this activity is scheduled to occur. Formerly named scheduled_date_time.'), + 'import' => TRUE, 'where' => 'civicrm_activity.activity_date_time', 'headerPattern' => '/(activity.)?date(.time$)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - 'activity_duration' => array( + ], + ], + 'activity_duration' => [ 'name' => 'duration', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Duration') , - 'description' => 'Planned or actual duration of activity expressed in minutes. Conglomerate of former duration_hours and duration_minutes.', - 'import' => true, + 'title' => ts('Duration'), + 'description' => ts('Planned or actual duration of activity expressed in minutes. Conglomerate of former duration_hours and duration_minutes.'), + 'import' => TRUE, 'where' => 'civicrm_activity.duration', 'headerPattern' => '/(activity.)?duration(s)?$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'activity_location' => array( + ], + ], + 'activity_location' => [ 'name' => 'location', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Location') , - 'description' => 'Location of the activity (optional, open text).', + 'title' => ts('Location'), + 'description' => ts('Location of the activity (optional, open text).'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_activity.location', 'headerPattern' => '/(activity.)?location$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'phone_id' => array( + ], + ], + 'phone_id' => [ 'name' => 'phone_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Phone (called) ID') , - 'description' => 'Phone ID of the number called (optional - used if an existing phone number is selected).', + 'title' => ts('Phone (called) ID'), + 'description' => ts('Phone ID of the number called (optional - used if an existing phone number is selected).'), + 'where' => 'civicrm_activity.phone_id', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_Phone', - 'html' => array( + 'html' => [ 'type' => 'EntityRef', - ) , - ) , - 'phone_number' => array( + ], + ], + 'phone_number' => [ 'name' => 'phone_number', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Phone (called) Number') , - 'description' => 'Phone number in case the number does not exist in the civicrm_phone table.', + 'title' => ts('Phone (called) Number'), + 'description' => ts('Phone number in case the number does not exist in the civicrm_phone table.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_activity.phone_number', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'activity_details' => array( + ], + ], + 'activity_details' => [ 'name' => 'details', 'type' => CRM_Utils_Type::T_LONGTEXT, - 'title' => ts('Details') , - 'description' => 'Details about the activity (agenda, notes, etc).', - 'import' => true, + 'title' => ts('Details'), + 'description' => ts('Details about the activity (agenda, notes, etc).'), + 'import' => TRUE, 'where' => 'civicrm_activity.details', 'headerPattern' => '/(activity.)?detail(s)?$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'RichTextEditor', - ) , - ) , - 'activity_status_id' => array( + ], + ], + 'activity_status_id' => [ 'name' => 'status_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity Status') , - 'description' => 'ID of the status this activity is currently in. Foreign key to civicrm_option_value.', - 'import' => true, + 'title' => ts('Activity Status'), + 'description' => ts('ID of the status this activity is currently in. Foreign key to civicrm_option_value.'), + 'import' => TRUE, 'where' => 'civicrm_activity.status_id', 'headerPattern' => '/(activity.)?status(.label$)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'activity_status', 'optionEditPath' => 'civicrm/admin/options/activity_status', - ) - ) , - 'priority_id' => array( + ], + ], + 'priority_id' => [ 'name' => 'priority_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Priority') , - 'description' => 'ID of the priority given to this activity. Foreign key to civicrm_option_value.', + 'title' => ts('Priority'), + 'description' => ts('ID of the priority given to this activity. Foreign key to civicrm_option_value.'), + 'where' => 'civicrm_activity.priority_id', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'priority', 'optionEditPath' => 'civicrm/admin/options/priority', - ) - ) , - 'parent_id' => array( + ], + ], + 'parent_id' => [ 'name' => 'parent_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Parent Activity Id') , - 'description' => 'Parent meeting ID (if this is a follow-up item). This is not currently implemented', + 'title' => ts('Parent Activity Id'), + 'description' => ts('Parent meeting ID (if this is a follow-up item). This is not currently implemented'), + 'where' => 'civicrm_activity.parent_id', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, 'FKClassName' => 'CRM_Activity_DAO_Activity', - ) , - 'activity_is_test' => array( + ], + 'activity_is_test' => [ 'name' => 'is_test', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Test') , - 'import' => true, + 'title' => ts('Test'), + 'import' => TRUE, 'where' => 'civicrm_activity.is_test', 'headerPattern' => '/(is.)?test(.activity)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - ) , - 'activity_medium_id' => array( + ], + ], + 'activity_medium_id' => [ 'name' => 'medium_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity Medium') , - 'description' => 'Activity Medium, Implicit FK to civicrm_option_value where option_group = encounter_medium.', + 'title' => ts('Activity Medium'), + 'description' => ts('Activity Medium, Implicit FK to civicrm_option_value where option_group = encounter_medium.'), + 'where' => 'civicrm_activity.medium_id', 'default' => 'NULL', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'encounter_medium', 'optionEditPath' => 'civicrm/admin/options/encounter_medium', - ) - ) , - 'is_auto' => array( + ], + ], + 'is_auto' => [ 'name' => 'is_auto', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Auto') , + 'title' => ts('Auto'), + 'where' => 'civicrm_activity.is_auto', + 'default' => '0', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - ) , - 'relationship_id' => array( + ], + 'relationship_id' => [ 'name' => 'relationship_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Relationship Id') , - 'description' => 'FK to Relationship ID', + 'title' => ts('Relationship Id'), + 'description' => ts('FK to Relationship ID'), + 'where' => 'civicrm_activity.relationship_id', 'default' => 'NULL', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Relationship', - ) , - 'is_current_revision' => array( + ], + 'is_current_revision' => [ 'name' => 'is_current_revision', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is this activity a current revision in versioning chain?') , - 'import' => true, + 'title' => ts('Is this activity a current revision in versioning chain?'), + 'import' => TRUE, 'where' => 'civicrm_activity.is_current_revision', 'headerPattern' => '/(is.)?(current.)?(revision|version(ing)?)/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'original_id' => array( + ], + ], + 'original_id' => [ 'name' => 'original_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Original Activity ID ') , - 'description' => 'Activity ID of the first activity record in versioning chain.', + 'title' => ts('Original Activity ID '), + 'description' => ts('Activity ID of the first activity record in versioning chain.'), + 'where' => 'civicrm_activity.original_id', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, 'FKClassName' => 'CRM_Activity_DAO_Activity', - ) , - 'activity_result' => array( + ], + 'activity_result' => [ 'name' => 'result', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Result') , - 'description' => 'Currently being used to store result id for survey activity, FK to option value.', + 'title' => ts('Result'), + 'description' => ts('Currently being used to store result id for survey activity, FK to option value.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_activity.result', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'activity_is_deleted' => array( + ], + ], + 'activity_is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Activity is in the Trash') , - 'import' => true, + 'title' => ts('Activity is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_activity.is_deleted', 'headerPattern' => '/(activity.)?(trash|deleted)/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'activity_campaign_id' => array( + ], + ], + 'activity_campaign_id' => [ 'name' => 'campaign_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Campaign') , - 'description' => 'The campaign for which this activity has been triggered.', - 'import' => true, + 'title' => ts('Campaign'), + 'description' => ts('The campaign for which this activity has been triggered.'), + 'import' => TRUE, 'where' => 'civicrm_activity.campaign_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, 'FKClassName' => 'CRM_Campaign_DAO_Campaign', - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_campaign', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'activity_engagement_level' => array( + ], + ], + 'activity_engagement_level' => [ 'name' => 'engagement_level', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Engagement Index') , - 'description' => 'Assign a specific level of engagement to this activity. Used for tracking constituents in ladder of engagement.', - 'import' => true, + 'title' => ts('Engagement Index'), + 'description' => ts('Assign a specific level of engagement to this activity. Used for tracking constituents in ladder of engagement.'), + 'import' => TRUE, 'where' => 'civicrm_activity.engagement_level', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'engagement_index', 'optionEditPath' => 'civicrm/admin/options/engagement_index', - ) - ) , - 'weight' => array( + ], + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Order') , + 'title' => ts('Order'), + 'where' => 'civicrm_activity.weight', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'is_star' => array( + ], + ], + 'is_star' => [ 'name' => 'is_star', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Starred') , - 'description' => 'Activity marked as favorite.', - 'import' => true, + 'title' => ts('Is Starred'), + 'description' => ts('Activity marked as favorite.'), + 'import' => TRUE, 'where' => 'civicrm_activity.is_star', 'headerPattern' => '/(activity.)?(star|favorite)/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_activity', + 'entity' => 'Activity', + 'bao' => 'CRM_Activity_BAO_Activity', + 'localizable' => 0, + ], + 'activity_created_date' => [ + 'name' => 'created_date', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Created Date'), + 'description' => ts('When was the activity was created.'), + 'required' => FALSE, + 'where' => 'civicrm_activity.created_date', + 'export' => TRUE, + 'default' => 'NULL', 'table_name' => 'civicrm_activity', 'entity' => 'Activity', 'bao' => 'CRM_Activity_BAO_Activity', 'localizable' => 0, - ) , - ); + ], + 'activity_modified_date' => [ + 'name' => 'modified_date', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Modified Date'), + 'description' => ts('When was the activity (or closely related entity) was created or modified or deleted.'), + 'required' => FALSE, + 'where' => 'civicrm_activity.modified_date', + 'export' => TRUE, + 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', + 'table_name' => 'civicrm_activity', + 'entity' => 'Activity', + 'bao' => 'CRM_Activity_BAO_Activity', + 'localizable' => 0, + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -682,10 +726,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'activity', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'activity', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -693,72 +738,78 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'activity', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'activity', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_source_record_id' => array( + $indices = [ + 'UI_source_record_id' => [ 'name' => 'UI_source_record_id', - 'field' => array( + 'field' => [ 0 => 'source_record_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::source_record_id', - ) , - 'UI_activity_type_id' => array( + ], + 'UI_activity_type_id' => [ 'name' => 'UI_activity_type_id', - 'field' => array( + 'field' => [ 0 => 'activity_type_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::activity_type_id', - ) , - 'index_activity_date_time' => array( + ], + 'index_activity_date_time' => [ 'name' => 'index_activity_date_time', - 'field' => array( + 'field' => [ 0 => 'activity_date_time', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::activity_date_time', - ) , - 'index_status_id' => array( + ], + 'index_status_id' => [ 'name' => 'index_status_id', - 'field' => array( + 'field' => [ 0 => 'status_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::status_id', - ) , - 'index_medium_id' => array( + ], + 'index_medium_id' => [ 'name' => 'index_medium_id', - 'field' => array( + 'field' => [ 0 => 'medium_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::medium_id', - ) , - 'index_is_current_revision' => array( + ], + 'index_is_current_revision' => [ 'name' => 'index_is_current_revision', - 'field' => array( + 'field' => [ 0 => 'is_current_revision', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::is_current_revision', - ) , - 'index_is_deleted' => array( + ], + 'index_is_deleted' => [ 'name' => 'index_is_deleted', - 'field' => array( + 'field' => [ 0 => 'is_deleted', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity::0::is_deleted', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Activity/DAO/ActivityContact.php b/CRM/Activity/DAO/ActivityContact.php index bb95eb6fddbc..290148d0e4b6 100644 --- a/CRM/Activity/DAO/ActivityContact.php +++ b/CRM/Activity/DAO/ActivityContact.php @@ -1,199 +1,188 @@ __table = 'civicrm_activity_contact'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'activity_id', 'civicrm_activity', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'activity_id', 'civicrm_activity', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity Contact ID') , - 'description' => 'Activity contact id', - 'required' => true, + 'title' => ts('Activity Contact ID'), + 'description' => ts('Activity contact id'), + 'required' => TRUE, + 'where' => 'civicrm_activity_contact.id', 'table_name' => 'civicrm_activity_contact', 'entity' => 'ActivityContact', 'bao' => 'CRM_Activity_BAO_ActivityContact', 'localizable' => 0, - ) , - 'activity_id' => array( + ], + 'activity_id' => [ 'name' => 'activity_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity ID') , - 'description' => 'Foreign key to the activity for this record.', - 'required' => true, + 'title' => ts('Activity ID'), + 'description' => ts('Foreign key to the activity for this record.'), + 'required' => TRUE, + 'where' => 'civicrm_activity_contact.activity_id', 'table_name' => 'civicrm_activity_contact', 'entity' => 'ActivityContact', 'bao' => 'CRM_Activity_BAO_ActivityContact', 'localizable' => 0, 'FKClassName' => 'CRM_Activity_DAO_Activity', - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID (match to contact)') , - 'description' => 'Foreign key to the contact for this record.', - 'required' => true, - 'import' => true, + 'title' => ts('Contact ID (match to contact)'), + 'description' => ts('Foreign key to the contact for this record.'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_activity_contact.contact_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_activity_contact', 'entity' => 'ActivityContact', 'bao' => 'CRM_Activity_BAO_ActivityContact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'record_type_id' => array( + ], + 'record_type_id' => [ 'name' => 'record_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Record Type ID') , - 'description' => 'Nature of this contact\'s role in the activity: 1 assignee, 2 creator, 3 focus or target.', + 'title' => ts('Record Type ID'), + 'description' => ts('Nature of this contact\'s role in the activity: 1 assignee, 2 creator, 3 focus or target.'), + 'where' => 'civicrm_activity_contact.record_type_id', 'table_name' => 'civicrm_activity_contact', 'entity' => 'ActivityContact', 'bao' => 'CRM_Activity_BAO_ActivityContact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'activity_contacts', 'optionEditPath' => 'civicrm/admin/options/activity_contacts', - ) - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -201,10 +190,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'activity_contact', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'activity_contact', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -212,36 +202,42 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'activity_contact', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'activity_contact', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_activity_contact' => array( + $indices = [ + 'UI_activity_contact' => [ 'name' => 'UI_activity_contact', - 'field' => array( + 'field' => [ 0 => 'contact_id', 1 => 'activity_id', 2 => 'record_type_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_activity_contact::1::contact_id::activity_id::record_type_id', - ) , - 'index_record_type' => array( + ], + 'index_record_type' => [ 'name' => 'index_record_type', - 'field' => array( + 'field' => [ 0 => 'activity_id', 1 => 'record_type_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_activity_contact::0::activity_id::record_type_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Activity/Form/Activity.php b/CRM/Activity/Form/Activity.php index d0b6dad586a6..37e37d735741 100644 --- a/CRM/Activity/Form/Activity.php +++ b/CRM/Activity/Form/Activity.php @@ -1,9 +1,9 @@ _fields = array( - 'subject' => array( + $this->_fields = [ + 'subject' => [ 'type' => 'text', 'label' => ts('Subject'), - 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', - 'subject' - ), - ), - 'duration' => array( - 'type' => 'text', + 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'activity_subject'), + ], + 'duration' => [ + 'type' => 'number', 'label' => ts('Duration'), - 'attributes' => array('size' => 4, 'maxlength' => 8), + 'attributes' => ['class' => 'four', 'min' => 1], 'required' => FALSE, - ), - 'location' => array( + ], + 'location' => [ 'type' => 'text', 'label' => ts('Location'), 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'location'), 'required' => FALSE, - ), - 'details' => array( + ], + 'details' => [ 'type' => 'wysiwyg', 'label' => ts('Details'), - 'attributes' => array('class' => 'huge'), + 'attributes' => ['class' => 'huge'], 'required' => FALSE, - ), - 'status_id' => array( + ], + 'status_id' => [ 'type' => 'select', 'required' => TRUE, - ), - 'priority_id' => array( + ], + 'priority_id' => [ 'type' => 'select', 'required' => TRUE, - ), - 'source_contact_id' => array( + ], + 'source_contact_id' => [ 'type' => 'entityRef', 'label' => ts('Added By'), 'required' => FALSE, - ), - 'target_contact_id' => array( + ], + 'target_contact_id' => [ 'type' => 'entityRef', 'label' => ts('With Contact'), - 'attributes' => array('multiple' => TRUE, 'create' => TRUE), - ), - 'assignee_contact_id' => array( + 'attributes' => ['multiple' => TRUE, 'create' => TRUE], + ], + 'assignee_contact_id' => [ 'type' => 'entityRef', 'label' => ts('Assigned to'), - 'attributes' => array( + 'attributes' => [ 'multiple' => TRUE, 'create' => TRUE, - 'api' => array('params' => array('is_deceased' => 0)), - ), - ), - 'followup_assignee_contact_id' => array( + 'api' => ['params' => ['is_deceased' => 0]], + ], + ], + 'activity_date_time' => [ + 'type' => 'datepicker', + 'label' => ts('Date'), + 'required' => TRUE, + ], + 'followup_assignee_contact_id' => [ 'type' => 'entityRef', 'label' => ts('Assigned to'), - 'attributes' => array( + 'attributes' => [ 'multiple' => TRUE, 'create' => TRUE, - 'api' => array('params' => array('is_deceased' => 0)), - ), - ), - 'followup_activity_type_id' => array( + 'api' => ['params' => ['is_deceased' => 0]], + ], + ], + 'followup_activity_type_id' => [ 'type' => 'select', 'label' => ts('Followup Activity'), - 'attributes' => array('' => '- ' . ts('select activity') . ' -') + $activityTypes, - 'extra' => array('class' => 'crm-select2'), - ), + 'attributes' => ['' => '- ' . ts('select activity') . ' -'] + $activityTypes, + 'extra' => ['class' => 'crm-select2'], + ], // Add optional 'Subject' field for the Follow-up Activiity, CRM-4491 - 'followup_activity_subject' => array( + 'followup_activity_subject' => [ 'type' => 'text', 'label' => ts('Subject'), - 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', - 'subject' - ), - ), - ); + 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'subject'), + ], + ]; } /** @@ -238,11 +260,11 @@ public function preProcess() { // Give the context. if (!isset($this->_context)) { - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); if (CRM_Contact_Form_Search::isSearchContext($this->_context)) { $this->_context = 'search'; } - elseif (!in_array($this->_context, array('dashlet', 'dashletFullscreen')) + elseif (!in_array($this->_context, ['dashlet', 'case', 'dashletFullscreen']) && $this->_currentlyViewedContactId ) { $this->_context = 'activity'; @@ -256,7 +278,7 @@ public function preProcess() { if ($this->_action & CRM_Core_Action::DELETE) { if (!CRM_Core_Permission::check('delete activities')) { - CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); + CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.')); } } @@ -279,18 +301,19 @@ public function preProcess() { // Check for required permissions, CRM-6264. if ($this->_activityId && - in_array($this->_action, array( + in_array($this->_action, [ CRM_Core_Action::UPDATE, CRM_Core_Action::VIEW, - )) && + ]) && !CRM_Activity_BAO_Activity::checkPermission($this->_activityId, $this->_action) ) { - CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); + CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.')); } if (($this->_action & CRM_Core_Action::VIEW) && CRM_Activity_BAO_Activity::checkPermission($this->_activityId, CRM_Core_Action::UPDATE) ) { $this->assign('permission', 'edit'); + $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions()); } if (!$this->_activityTypeId && $this->_activityId) { @@ -300,32 +323,7 @@ public function preProcess() { ); } - // Assigning Activity type name. - if ($this->_activityTypeId) { - $activityTName = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId, 'label'); - if ($activityTName[$this->_activityTypeId]) { - $this->_activityTypeName = $activityTName[$this->_activityTypeId]; - $this->assign('activityTName', $activityTName[$this->_activityTypeId]); - } - } - - // Set title. - if (isset($activityTName)) { - $activityName = CRM_Utils_Array::value($this->_activityTypeId, $activityTName); - $this->assign('pageTitle', ts('%1 Activity', array(1 => $activityName))); - - if ($this->_currentlyViewedContactId) { - $displayName = CRM_Contact_BAO_Contact::displayName($this->_currentlyViewedContactId); - // Check if this is default domain contact CRM-10482. - if (CRM_Contact_BAO_Contact::checkDomainContact($this->_currentlyViewedContactId)) { - $displayName .= ' (' . ts('default organization') . ')'; - } - CRM_Utils_System::setTitle($displayName . ' - ' . $activityName); - } - else { - CRM_Utils_System::setTitle(ts('%1 Activity', array(1 => $activityName))); - } - } + $this->assignActivityType(); // Check the mode when this form is called either single or as // search task action. @@ -377,7 +375,7 @@ public function preProcess() { if ($this->_action & CRM_Core_Action::VIEW) { // Get the tree of custom fields. - $this->_groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', $this, + $this->_groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', NULL, $this->_activityId, 0, $this->_activityTypeId ); } @@ -415,12 +413,12 @@ public function preProcess() { } $this->assign('searchKey', $qfKey); } - elseif (in_array($this->_context, array( + elseif (in_array($this->_context, [ 'standalone', 'home', 'dashlet', 'dashletFullscreen', - )) + ]) ) { $urlParams = 'reset=1'; $urlString = 'civicrm/dashboard'; @@ -439,6 +437,7 @@ public function preProcess() { || $path == 'civicrm/contact/search/advanced' || $path == 'civicrm/contact/search/custom' || $path == 'civicrm/group/search' + || $path == 'civicrm/contact/search/builder' ) { $urlString = $path; } @@ -462,6 +461,7 @@ public function preProcess() { } // when custom data is included in this page + $this->assign('cid', $this->_currentlyViewedContactId); if (!empty($_POST['hidden_custom'])) { // We need to set it in the session for the code below to work. // CRM-3014 @@ -494,17 +494,40 @@ public function preProcess() { $this->_values = $this->get('values'); if (!is_array($this->_values)) { - $this->_values = array(); + $this->_values = []; if (isset($this->_activityId) && $this->_activityId) { - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; CRM_Activity_BAO_Activity::retrieve($params, $this->_values); } + $this->set('values', $this->_values); } if ($this->_action & CRM_Core_Action::UPDATE) { + // We filter out alternatives, in case this is a stored e-mail, before sending to front-end + if (isset($this->_values['details'])) { + $this->_values['details'] = CRM_Utils_String::stripAlternatives($this->_values['details']) ?: ''; + } + + if ($this->_activityTypeName === 'Inbound Email' && + !CRM_Core_Permission::check('edit inbound email basic information and content') + ) { + $this->_fields['details']['type'] = 'static'; + } + CRM_Core_Form_RecurringEntity::preProcess('civicrm_activity'); } + + if ($this->_action & CRM_Core_Action::VIEW) { + $url = CRM_Utils_System::url(implode("/", $this->urlPath), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}"); + CRM_Utils_Recent::add(CRM_Utils_Array::value('subject', $this->_values, ts('(no subject)')), + $url, + $this->_values['id'], + 'Activity', + $this->_values['source_contact_id'], + $this->_values['source_contact'] + ); + } } /** @@ -519,16 +542,6 @@ public function setDefaultValues() { $defaults = $this->_values + CRM_Core_Form_RecurringEntity::setDefaultValues(); // if we're editing... if (isset($this->_activityId)) { - if (empty($defaults['activity_date_time'])) { - list($defaults['activity_date_time'], $defaults['activity_date_time_time']) = CRM_Utils_Date::setDateDefaults(NULL, 'activityDateTime'); - } - elseif ($this->_action & CRM_Core_Action::UPDATE) { - $this->assign('current_activity_date_time', $defaults['activity_date_time']); - list($defaults['activity_date_time'], - $defaults['activity_date_time_time'] - ) = CRM_Utils_Date::setDateDefaults($defaults['activity_date_time'], 'activityDateTime'); - list($defaults['repetition_start_date'], $defaults['repetition_start_date_time']) = CRM_Utils_Date::setDateDefaults($defaults['activity_date_time'], 'activityDateTime'); - } if ($this->_context != 'standalone') { $this->assign('target_contact_value', @@ -553,9 +566,10 @@ public function setDefaultValues() { $defaults['source_contact_id'] = $this->_sourceContactId; $defaults['target_contact_id'] = $this->_targetContactId; + } - list($defaults['activity_date_time'], $defaults['activity_date_time_time']) - = CRM_Utils_Date::setDateDefaults(NULL, 'activityDateTime'); + if (empty($defaults['activity_date_time'])) { + $defaults['activity_date_time'] = date('Y-m-d H:i:s'); } if ($this->_activityTypeId) { @@ -567,10 +581,10 @@ public function setDefaultValues() { } // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle - if ($this->_action == 2 && !empty($defaults['target_contact_id'])) { + if ($this->_action == CRM_Core_Action::UPDATE && !empty($defaults['target_contact_id'])) { $count = count(is_array($defaults['target_contact_id']) ? $defaults['target_contact_id'] : explode(',', $defaults['target_contact_id'])); if ($count > 50) { - $this->freeze(array('target_contact_id')); + $this->freeze(['target_contact_id']); } } @@ -592,6 +606,12 @@ public function setDefaultValues() { return $defaults; } + /** + * Build Quick form. + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ public function buildQuickForm() { if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) { //enable form element (ActivityLinks sets this true) @@ -601,18 +621,18 @@ public function buildQuickForm() { if ($this->_action & CRM_Core_Action::RENEW) { $button = ts('Restore'); } - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'next', 'name' => $button, 'spacing' => '         ', 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); return; } @@ -623,11 +643,11 @@ public function buildQuickForm() { $this->assign('suppressForm', FALSE); $element = &$this->add('select', 'activity_type_id', ts('Activity Type'), - array('' => '- ' . ts('select') . ' -') + $this->_fields['followup_activity_type_id']['attributes'], - FALSE, array( - 'onchange' => "CRM.buildCustomData( 'Activity', this.value );", + ['' => '- ' . ts('select') . ' -'] + $this->_fields['followup_activity_type_id']['attributes'], + FALSE, [ + 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});", 'class' => 'crm-select2 required', - ) + ] ); // Freeze for update mode. @@ -646,7 +666,7 @@ public function buildQuickForm() { $required = !empty($values['required']); if ($values['type'] == 'select' && empty($attribute)) { - $this->addSelect($field, array('entity' => 'activity'), $required); + $this->addSelect($field, ['entity' => 'activity'], $required); } elseif ($values['type'] == 'entityRef') { $this->addEntityRef($field, $values['label'], $attribute, $required); @@ -666,7 +686,7 @@ public function buildQuickForm() { CRM_Campaign_BAO_Campaign::accessCampaign() ) { $buildEngagementLevel = TRUE; - $this->addSelect('engagement_level', array('entity' => 'activity')); + $this->addSelect('engagement_level', ['entity' => 'activity']); $this->addRule('engagement_level', ts('Please enter the engagement index as a number (integers only).'), 'positiveInteger' @@ -690,7 +710,7 @@ public function buildQuickForm() { $responseOptions = CRM_Campaign_BAO_Survey::getResponsesOptions($surveyId); if ($responseOptions) { $this->add('select', 'result', ts('Result'), - array('' => ts('- select -')) + array_combine($responseOptions, $responseOptions) + ['' => ts('- select -')] + array_combine($responseOptions, $responseOptions) ); } $surveyTitle = NULL; @@ -702,18 +722,26 @@ public function buildQuickForm() { } $this->assign('surveyActivity', $this->_isSurveyActivity); - // this option should be available only during add mode - if ($this->_action != CRM_Core_Action::UPDATE) { - $this->add('advcheckbox', 'is_multi_activity', ts('Create a separate activity for each contact.')); + // Add the "Activity Separation" field + $actionIsAdd = ($this->_action != CRM_Core_Action::UPDATE && $this->_action != CRM_Core_Action::VIEW); + $separationIsPossible = $this->supportsActivitySeparation; + if ($actionIsAdd && $separationIsPossible) { + $this->addRadio( + 'separation', + ts('Activity Separation'), + [ + 'separate' => ts('Create separate activities for each contact'), + 'combined' => ts('Create one activity with all contacts together'), + ] + ); } $this->addRule('duration', ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger' ); - $this->addDateTime('activity_date_time', ts('Date'), TRUE, array('formatType' => 'activityDateTime')); // Add followup date. - $this->addDateTime('followup_date', ts('in'), FALSE, array('formatType' => 'activityDateTime')); + $this->add('datepicker', 'followup_date', ts('in')); // Only admins and case-workers can change the activity source if (!CRM_Core_Permission::check('administer CiviCRM') && $this->_context != 'caseActivity') { @@ -728,11 +756,15 @@ public function buildQuickForm() { $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_activity'); if (!empty($tags)) { - $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, array('class' => 'huge', 'placeholder' => ts('- select -'), 'multiple' => TRUE)); + $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [ + 'class' => 'huge', + 'placeholder' => ts('- select -'), + 'multiple' => TRUE, + ]); } // we need to hide activity tagset for special activities - $specialActivities = array('Open Case'); + $specialActivities = ['Open Case']; if (!in_array($this->_activityTypeName, $specialActivities)) { // build tag widget @@ -748,48 +780,46 @@ public function buildQuickForm() { // form should be frozen for view mode $this->freeze(); - $buttons = array(); - $buttons[] = array( - 'type' => 'cancel', - 'name' => ts('Done'), - ); - $this->addButtons($buttons); + $this->addButtons([ + [ + 'type' => 'cancel', + 'name' => ts('Done'), + ], + ]); } else { - $message = array( - 'completed' => ts('Are you sure? This is a COMPLETED activity with the DATE in the FUTURE. Click Cancel to change the date / status. Otherwise, click OK to save.'), - 'scheduled' => ts('Are you sure? This is a SCHEDULED activity with the DATE in the PAST. Click Cancel to change the date / status. Otherwise, click OK to save.'), - ); - $js = array('onclick' => "return activityStatus(" . json_encode($message) . ");"); - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'upload', 'name' => ts('Save'), - 'js' => $js, 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); } if ($this->_activityTypeFile) { $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}"; $className::buildQuickForm($this); - $this->addFormRule(array($className, 'formRule'), $this); + $this->addFormRule([$className, 'formRule'], $this); } - $this->addFormRule(array('CRM_Activity_Form_Activity', 'formRule'), $this); + $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this); - if (Civi::settings()->get('activity_assignee_notification')) { - $this->assign('activityAssigneeNotification', TRUE); + $doNotNotifyAssigneeFor = (array) Civi::settings() + ->get('do_not_notify_assignees_for'); + if (($this->_activityTypeId && in_array($this->_activityTypeId, $doNotNotifyAssigneeFor)) || !Civi::settings() + ->get('activity_assignee_notification')) { + $this->assign('activityAssigneeNotification', FALSE); } else { - $this->assign('activityAssigneeNotification', FALSE); + $this->assign('activityAssigneeNotification', TRUE); } + $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor); } /** @@ -809,29 +839,41 @@ public static function formRule($fields, $files, $self) { if (CRM_Utils_Array::value('_qf_Activity_next_', $fields) == 'Delete') { return TRUE; } - $errors = array(); + $errors = []; if ((array_key_exists('activity_type_id', $fields) || !$self->_single) && empty($fields['activity_type_id'])) { $errors['activity_type_id'] = ts('Activity Type is a required field'); } - if (CRM_Utils_Array::value('activity_type_id', $fields) == 3 && - CRM_Utils_Array::value('status_id', $fields) == 1 - ) { - $errors['status_id'] = ts('You cannot record scheduled email activity.'); - } - elseif (CRM_Utils_Array::value('activity_type_id', $fields) == 4 && - CRM_Utils_Array::value('status_id', $fields) == 1 - ) { - $errors['status_id'] = ts('You cannot record scheduled SMS activity.'); + $activity_type_id = CRM_Utils_Array::value('activity_type_id', $fields); + $activity_status_id = CRM_Utils_Array::value('status_id', $fields); + $scheduled_status_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'); + + if ($activity_type_id && $activity_status_id == $scheduled_status_id) { + if ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) { + $errors['status_id'] = ts('You cannot record scheduled email activity.'); + } + elseif ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) { + $errors['status_id'] = ts('You cannot record scheduled SMS activity'); + } } if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) { - $errors['followup_date_time'] = ts('Followup date is a required field.'); + $errors['followup_date'] = ts('Followup date is a required field.'); } // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515. if ((!empty($fields['followup_activity_subject']) || !empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) { $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.'); } + + // Check that a value has been set for the "activity separation" field if needed + $separationIsPossible = $self->supportsActivitySeparation; + $actionIsAdd = $self->_action == CRM_Core_Action::ADD; + $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE; + $separationFieldIsEmpty = empty($fields['separation']); + if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) { + $errors['separation'] = ts('Activity Separation is a required field.'); + } + return $errors; } @@ -840,23 +882,39 @@ public static function formRule($fields, $files, $self) { * * * @param array $params + * * @return array|null + * @throws \CiviCRM_API3_Exception */ public function postProcess($params = NULL) { if ($this->_action & CRM_Core_Action::DELETE) { - $deleteParams = array('id' => $this->_activityId); - $moveToTrash = CRM_Case_BAO_Case::isCaseActivity($this->_activityId); - CRM_Activity_BAO_Activity::deleteActivity($deleteParams, $moveToTrash); - - // delete tags for the entity - $tagParams = array( - 'entity_table' => 'civicrm_activity', - 'entity_id' => $this->_activityId, - ); + // Look up any repeat activities to be deleted. + $activityIds = array_column(CRM_Core_BAO_RecurringEntity::getEntitiesFor($this->_activityId, 'civicrm_activity', TRUE, NULL), 'id'); + if (!$activityIds) { + // There are no repeat activities to delete - just this one. + $activityIds = [$this->_activityId]; + } + + // Delete each activity. + foreach ($activityIds as $activityId) { + $deleteParams = ['id' => $activityId]; + $moveToTrash = CRM_Case_BAO_Case::isCaseActivity($activityId); + CRM_Activity_BAO_Activity::deleteActivity($deleteParams, $moveToTrash); + + // delete tags for the entity + $tagParams = [ + 'entity_table' => 'civicrm_activity', + 'entity_id' => $activityId, + ]; - CRM_Core_BAO_EntityTag::del($tagParams); + CRM_Core_BAO_EntityTag::del($tagParams); + } + + CRM_Core_Session::setStatus( + ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]), + ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success' + ); - CRM_Core_Session::setStatus(ts("Selected Activity has been deleted successfully."), ts('Record Deleted'), 'success'); return NULL; } @@ -887,16 +945,13 @@ public function postProcess($params = NULL) { ); } - // store the date with proper format - $params['activity_date_time'] = CRM_Utils_Date::processDate($params['activity_date_time'], $params['activity_date_time_time']); - // format params as arrays - foreach (array('target', 'assignee', 'followup_assignee') as $name) { + foreach (['target', 'assignee', 'followup_assignee'] as $name) { if (!empty($params["{$name}_contact_id"])) { $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]); } else { - $params["{$name}_contact_id"] = array(); + $params["{$name}_contact_id"] = []; } } @@ -916,13 +971,15 @@ public function postProcess($params = NULL) { $this->_activityId ); - $activity = array(); + $params['is_multi_activity'] = CRM_Utils_Array::value('separation', $params) == 'separate'; + + $activity = []; if (!empty($params['is_multi_activity']) && !CRM_Utils_Array::crmIsEmptyArray($params['target_contact_id']) ) { $targetContacts = $params['target_contact_id']; foreach ($targetContacts as $targetContactId) { - $params['target_contact_id'] = array($targetContactId); + $params['target_contact_id'] = [$targetContactId]; // save activity $activity[] = $this->processActivity($params); } @@ -932,7 +989,18 @@ public function postProcess($params = NULL) { $activity = $this->processActivity($params); } - $activityIds = empty($this->_activityIds) ? array($this->_activityId) : $this->_activityIds; + // Redirect to contact page or activity view in standalone mode + if ($this->_context == 'standalone') { + if (count($params['target_contact_id']) == 1) { + $url = CRM_Utils_System::url('civicrm/contact/view', ['cid' => CRM_Utils_Array::first($params['target_contact_id']), 'selectedChild' => 'activity']); + } + else { + $url = CRM_Utils_System::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId]); + } + CRM_Core_Session::singleton()->pushUserContext($url); + } + + $activityIds = empty($this->_activityIds) ? [$this->_activityId] : $this->_activityIds; foreach ($activityIds as $activityId) { // set params for repeat configuration in create mode $params['entity_id'] = $activityId; @@ -951,7 +1019,7 @@ public function postProcess($params = NULL) { $params['schedule_reminder_id'] = $scheduleReminderDetails->id; } } - $params['dateColumns'] = array('activity_date_time'); + $params['dateColumns'] = ['activity_date_time']; // Set default repetition start if it was not provided. if (empty($params['repetition_start_date'])) { @@ -960,20 +1028,20 @@ public function postProcess($params = NULL) { // unset activity id unset($params['id']); - $linkedEntities = array( - array( + $linkedEntities = [ + [ 'table' => 'civicrm_activity_contact', - 'findCriteria' => array( + 'findCriteria' => [ 'activity_id' => $activityId, - ), - 'linkedColumns' => array('activity_id'), + ], + 'linkedColumns' => ['activity_id'], 'isRecurringEntityRecord' => FALSE, - ), - ); + ], + ]; CRM_Core_Form_RecurringEntity::postProcess($params, 'civicrm_activity', $linkedEntities); } - return array('activity' => $activity); + return ['activity' => $activity]; } /** @@ -985,8 +1053,8 @@ public function postProcess($params = NULL) { * @return self|null|object */ protected function processActivity(&$params) { - $activityAssigned = array(); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityAssigned = []; + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); // format assignee params if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) { @@ -1006,14 +1074,13 @@ protected function processActivity(&$params) { $activity = CRM_Activity_BAO_Activity::create($params); // add tags if exists - $tagParams = array(); + $tagParams = []; if (!empty($params['tag'])) { if (!is_array($params['tag'])) { $params['tag'] = explode(',', $params['tag']); } - foreach ($params['tag'] as $tag) { - $tagParams[$tag] = 1; - } + + $tagParams = array_fill_keys($params['tag'], 1); } // Save static tags. @@ -1047,15 +1114,17 @@ protected function processActivity(&$params) { // send copy to assignee contacts.CRM-4509 $mailStatus = ''; - if (Civi::settings()->get('activity_assignee_notification')) { - $activityIDs = array($activity->id); + if (Civi::settings()->get('activity_assignee_notification') + && !in_array($activity->activity_type_id, Civi::settings() + ->get('do_not_notify_assignees_for'))) { + $activityIDs = [$activity->id]; if ($followupActivity) { - $activityIDs = array_merge($activityIDs, array($followupActivity->id)); + $activityIDs = array_merge($activityIDs, [$followupActivity->id]); } $assigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($activityIDs, TRUE, FALSE); if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) { - $mailToContacts = array(); + $mailToContacts = []; // Build an associative array with unique email addresses. foreach ($activityAssigned as $id => $dnc) { @@ -1072,7 +1141,7 @@ protected function processActivity(&$params) { // Also send email to follow-up activity assignees if set if ($followupActivity) { - $mailToFollowupContacts = array(); + $mailToFollowupContacts = []; foreach ($assigneeContacts as $values) { if ($values['activity_id'] == $followupActivity->id) { $mailToFollowupContacts[$values['email']] = $values; @@ -1093,11 +1162,11 @@ protected function processActivity(&$params) { } CRM_Core_Session::setStatus(ts('Activity %1 has been saved. %2 %3', - array( + [ 1 => $subject, 2 => $followupStatus, 3 => $mailStatus, - ) + ] ), ts('Saved'), 'success'); return $activity; @@ -1156,4 +1225,46 @@ public function endPostProcess(&$params, &$activity) { } } + /** + * For the moment keeping this the same as the original pulled from preProcess(). Also note the "s" at the end of the function name - planning to change that but in baby steps. + * + * @return string[] + */ + public function getActivityTypeDisplayLabels() { + return CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId, 'label'); + } + + /** + * For the moment this is just pulled from preProcess + */ + public function assignActivityType() { + if ($this->_activityTypeId) { + $activityTypeDisplayLabels = $this->getActivityTypeDisplayLabels(); + if ($activityTypeDisplayLabels[$this->_activityTypeId]) { + $this->_activityTypeName = $activityTypeDisplayLabels[$this->_activityTypeId]; + + // At the moment this is duplicating other code in this section, but refactoring in small steps. + $activityTypeObj = new CRM_Activity_BAO_ActivityType($this->_activityTypeId); + $this->assign('activityTypeNameAndLabel', $activityTypeObj->getActivityType()); + } + // Set title. + if (isset($activityTypeDisplayLabels)) { + // FIXME - it's not clear why the if line just above is needed here and why we can't just set this once above and re-use. What is interesting, but can't possibly be the reason, is that the first if block will fail if the label is the string '0', whereas this one won't. But who would have an activity type called '0'? + $activityTypeDisplayLabel = CRM_Utils_Array::value($this->_activityTypeId, $activityTypeDisplayLabels); + + if ($this->_currentlyViewedContactId) { + $displayName = CRM_Contact_BAO_Contact::displayName($this->_currentlyViewedContactId); + // Check if this is default domain contact CRM-10482. + if (CRM_Contact_BAO_Contact::checkDomainContact($this->_currentlyViewedContactId)) { + $displayName .= ' (' . ts('default organization') . ')'; + } + CRM_Utils_System::setTitle($displayName . ' - ' . $activityTypeDisplayLabel); + } + else { + CRM_Utils_System::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel])); + } + } + } + } + } diff --git a/CRM/Activity/Form/ActivityFilter.php b/CRM/Activity/Form/ActivityFilter.php index ef687781c9ea..472c5b542c60 100644 --- a/CRM/Activity/Form/ActivityFilter.php +++ b/CRM/Activity/Form/ActivityFilter.php @@ -1,9 +1,9 @@ add('select', 'activity_type_filter_id', ts('Include'), array('' => ts('- all activity type(s) -')) + $activityOptions); - $this->add('select', 'activity_type_exclude_filter_id', ts('Exclude'), array('' => ts('- select activity type -')) + $activityOptions); + $this->add('select', 'activity_type_filter_id', ts('Include'), $activityOptions, FALSE, ['class' => 'crm-select2', 'multiple' => TRUE, 'placeholder' => ts('- all activity type(s) -')]); + $this->add('select', 'activity_type_exclude_filter_id', ts('Exclude'), $activityOptions, FALSE, ['class' => 'crm-select2', 'multiple' => TRUE, 'placeholder' => ts('- no types excluded -')]); + $this->addDatePickerRange('activity_date_time', ts('Date')); + $this->addSelect('status_id', + ['entity' => 'activity', 'multiple' => 'multiple', 'option_url' => NULL, 'placeholder' => ts('- any -')] + ); + $this->assign('suppressForm', TRUE); } @@ -56,12 +62,13 @@ public function buildQuickForm() { */ public function setDefaultValues() { // CRM-11761 retrieve user's activity filter preferences - $defaults = array(); - $userID = CRM_Core_Session::getLoggedInContactID(); - if ($userID) { - $defaults = Civi::service('settings_manager') - ->getBagByContact(NULL, $userID) - ->get('activity_tab_filter'); + $defaults = []; + if (Civi::settings()->get('preserve_activity_tab_filter') && (CRM_Core_Session::getLoggedInContactID())) { + $defaults = Civi::contactSettings()->get('activity_tab_filter'); + } + // set Activity status 'Scheduled' by default only for dashlet + elseif (strstr(CRM_Utils_Array::value('q', $_GET), 'dashlet')) { + $defaults['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'); } return $defaults; } diff --git a/CRM/Activity/Form/ActivityLinks.php b/CRM/Activity/Form/ActivityLinks.php index 384c84be0c31..36a27d14b39c 100644 --- a/CRM/Activity/Form/ActivityLinks.php +++ b/CRM/Activity/Form/ActivityLinks.php @@ -1,9 +1,9 @@ 'activity_type', 'is_active' => 1, - 'options' => array('limit' => 0, 'sort' => 'weight'), - ))); + 'options' => ['limit' => 0, 'sort' => 'weight'], + ])); - $activityTypes = array(); + $activityTypes = []; foreach ($allTypes as $act) { $url = 'civicrm/activity/add'; @@ -63,8 +64,8 @@ public static function commonBuildQuickForm($self) { if (!CRM_Utils_Mail::validOutBoundMail() || !$contactId) { continue; } - list($name, $email, $doNotEmail, $onHold, $isDeseased) = CRM_Contact_BAO_Contact::getContactDetails($contactId); - if (!$doNotEmail && $email && !$isDeseased) { + list($name, $email, $doNotEmail, $onHold, $isDeceased) = CRM_Contact_BAO_Contact::getContactDetails($contactId); + if (!$doNotEmail && $email && !$isDeceased) { $url = 'civicrm/activity/email/add'; $act['label'] = ts('Send an Email'); } @@ -73,13 +74,26 @@ public static function commonBuildQuickForm($self) { } } elseif ($act['name'] == 'SMS') { - if (!$contactId || !CRM_SMS_BAO_Provider::activeProviderCount()) { + if (!$contactId || !CRM_SMS_BAO_Provider::activeProviderCount() || !CRM_Core_Permission::check('send SMS')) { continue; } // Check for existence of a mobile phone and ! do not SMS privacy setting - $mobileTypeID = CRM_Core_OptionGroup::getValue('phone_type', 'Mobile', 'name'); - list($name, $phone, $doNotSMS) = CRM_Contact_BAO_Contact_Location::getPhoneDetails($contactId, $mobileTypeID); - if (!$doNotSMS && $phone) { + try { + $phone = civicrm_api3('Phone', 'getsingle', [ + 'contact_id' => $contactId, + 'phone_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile'), + 'return' => ['phone', 'contact_id'], + 'options' => ['limit' => 1, 'sort' => "is_primary DESC"], + 'api.Contact.getsingle' => [ + 'id' => '$value.contact_id', + 'return' => 'do_not_sms', + ], + ]); + } + catch (CiviCRM_API3_Exception $e) { + continue; + } + if (!$phone['api.Contact.getsingle']['do_not_sms'] && $phone['phone']) { $url = 'civicrm/activity/sms/add'; } else { @@ -95,7 +109,7 @@ public static function commonBuildQuickForm($self) { $act['url'] = CRM_Utils_System::url($url, "{$urlParams}{$act['value']}", FALSE, NULL, FALSE ); - $act += array('icon' => 'fa-plus-square-o'); + $act += ['icon' => 'fa-plus-square-o']; $activityTypes[$act['value']] = $act; } diff --git a/CRM/Activity/Form/ActivityView.php b/CRM/Activity/Form/ActivityView.php index 254e5daf687a..1c643ff6c72f 100644 --- a/CRM/Activity/Form/ActivityView.php +++ b/CRM/Activity/Form/ActivityView.php @@ -1,9 +1,9 @@ pushUserContext($url); - $defaults = array(); - $params = array('id' => $activityId); + $defaults = []; + $params = ['id' => $activityId]; CRM_Activity_BAO_Activity::retrieve($params, $defaults); // Set activity type name and description to template. list($activityTypeName, $activityTypeDescription) = CRM_Core_BAO_OptionValue::getActivityTypeDetails($defaults['activity_type_id']); + // activityTypeName - dev/core#1116-unknown-if-ok + // It seems like activityTypeName is no longer used? Description is still used though. See PR notes for more details. $this->assign('activityTypeName', $activityTypeName); $this->assign('activityTypeDescription', $activityTypeDescription); @@ -108,20 +110,29 @@ public function preProcess() { $values['attachment'] = CRM_Core_BAO_File::attachmentInfo('civicrm_activity', $activityId); $this->assign('values', $values); + + $url = CRM_Utils_System::url(implode("/", $this->urlPath), "reset=1&id={$activityId}&action=view&cid={$values['source_contact_id']}"); + CRM_Utils_Recent::add($this->_values['subject'], + $url, + $values['id'], + 'Activity', + $values['source_contact_id'], + $values['source_contact'] + ); } /** * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'cancel', 'name' => ts('Done'), 'spacing' => '         ', 'isDefault' => TRUE, - ), - ) + ], + ] ); } diff --git a/CRM/Activity/Form/Search.php b/CRM/Activity/Form/Search.php index 1ed11f3ddf5b..4ede56c43631 100644 --- a/CRM/Activity/Form/Search.php +++ b/CRM/Activity/Form/Search.php @@ -1,9 +1,9 @@ _actionButtonName = $this->getButtonName('next', 'action'); $this->_done = FALSE; - $this->defaults = array(); - - // we allow the controller to set force/reset externally, useful when we are being - // driven by the wizard framework - $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean'); - $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE); - $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'search'); - $this->assign("context", $this->_context); + $this->loadStandardSearchOptionsFromUrl(); // get user submitted values // get it from controller only if form has been submitted, else preProcess has set this @@ -98,6 +98,15 @@ public function preProcess() { } else { $this->_formValues = $this->get('formValues'); + + if ($this->_force) { + // If we force the search then merge form values with url values + // and set submit values to form values. + // @todo this is not good security practice. Instead define the fields in metadata & use + // getEntityDefaults. + $this->_formValues = array_merge((array) $this->_formValues, CRM_Utils_Request::exportValues()); + $this->_submitValues = $this->_formValues; + } } if (empty($this->_formValues)) { @@ -163,9 +172,7 @@ public function buildQuickForm() { $this->addRowSelectors($rows); } - $permission = CRM_Core_Permission::getPermission(); - - $this->addTaskMenu(CRM_Activity_Task::permissionedTaskTitles($permission)); + $this->addTaskMenu(CRM_Activity_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission())); } } @@ -189,23 +196,20 @@ public function postProcess() { } $this->_done = TRUE; - + $this->setFormValues(); if (!empty($_POST)) { - $this->_formValues = $this->controller->exportValues($this->_name); - $specialParams = array( + $specialParams = [ 'activity_type_id', 'status_id', 'priority_id', - 'activity_text', - ); - $changeNames = array( + ]; + $changeNames = [ 'status_id' => 'activity_status_id', 'priority_id' => 'activity_priority_id', - ); + ]; CRM_Contact_BAO_Query::processSpecialFormValue($this->_formValues, $specialParams, $changeNames); } - $this->fixFormValues(); if (isset($this->_ssID) && empty($_POST)) { @@ -317,60 +321,9 @@ public function fixFormValues() { } } - // Added for membership search - - $signupType = CRM_Utils_Request::retrieve('signupType', 'Positive'); - - if ($signupType) { - $this->_formValues['activity_role'] = 1; - $this->_defaults['activity_role'] = 1; - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name'); - - $renew = CRM_Utils_Array::key('Membership Renewal', $activityTypes); - $signup = CRM_Utils_Array::key('Membership Signup', $activityTypes); - - switch ($signupType) { - case 3: // signups and renewals - $this->_formValues['activity_type_id'][$renew] = 1; - $this->_defaults['activity_type_id'][$renew] = 1; - case 1: // signups only - $this->_formValues['activity_type_id'][$signup] = 1; - $this->_defaults['activity_type_id'][$signup] = 1; - break; - - case 2: // renewals only - $this->_formValues['activity_type_id'][$renew] = 1; - $this->_defaults['activity_type_id'][$renew] = 1; - break; - } - } - - $dateLow = CRM_Utils_Request::retrieve('dateLow', 'String'); - - if ($dateLow) { - $dateLow = date('m/d/Y', strtotime($dateLow)); - $this->_formValues['activity_date_relative'] = 0; - $this->_defaults['activity_date_relative'] = 0; - $this->_formValues['activity_date_low'] = $dateLow; - $this->_defaults['activity_date_low'] = $dateLow; - } - - $dateHigh = CRM_Utils_Request::retrieve('dateHigh', 'String'); - - if ($dateHigh) { - // Activity date time assumes midnight at the beginning of the date - // This sets it to almost midnight at the end of the date - /* if ($dateHigh <= 99999999) { - $dateHigh = 1000000 * $dateHigh + 235959; - } */ - $dateHigh = date('m/d/Y', strtotime($dateHigh)); - $this->_formValues['activity_date_relative'] = 0; - $this->_defaults['activity_date_relative'] = 0; - $this->_formValues['activity_date_high'] = $dateHigh; - $this->_defaults['activity_date_high'] = $dateHigh; - } - // Enable search activity by custom value + // @todo this is not good security practice. Instead define entity fields in metadata & + // use getEntity Defaults $requestParams = CRM_Utils_Request::exportValues(); foreach (array_keys($requestParams) as $key) { if (substr($key, 0, 7) != 'custom_') { @@ -407,4 +360,8 @@ public function getTitle() { return ts('Find Activities'); } + protected function getEntityMetadata() { + return CRM_Activity_BAO_Query::getSearchFieldMetadata(); + } + } diff --git a/CRM/Activity/Form/Task.php b/CRM/Activity/Form/Task.php index 9bc63a052e41..d64c173297b1 100644 --- a/CRM/Activity/Form/Task.php +++ b/CRM/Activity/Form/Task.php @@ -1,9 +1,9 @@ _activityHolderIds = array(); + public static function preProcessCommon(&$form) { + $form->_activityHolderIds = []; $values = $form->controller->exportValues($form->get('searchFormName')); @@ -93,7 +65,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $activityTasks = CRM_Activity_Task::tasks(); $form->assign('taskName', $activityTasks[$form->_task]); - $ids = array(); + $ids = []; if ($values['radio_ts'] == 'ts_sel') { foreach ($values as $name => $value) { if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) { @@ -113,7 +85,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $activityClause = NULL; $components = CRM_Core_Component::getNames(); - $componentClause = array(); + $componentClause = []; foreach ($components as $componentID => $componentName) { if ($componentName != 'CiviCase' && !CRM_Core_Permission::check("access $componentName")) { $componentClause[] = " (activity_type.component_id IS NULL OR activity_type.component_id <> {$componentID}) "; @@ -164,7 +136,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { public function setContactIDs() { $IDs = implode(',', $this->_activityHolderIds); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $query = " SELECT contact_id @@ -191,17 +163,17 @@ public function setContactIDs() { * @param bool $submitOnce */ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) { - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => $nextType, 'name' => $title, 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => $backType, 'name' => ts('Cancel'), - ), - )); + ], + ]); } } diff --git a/CRM/Activity/Form/Task/AddToTag.php b/CRM/Activity/Form/Task/AddToTag.php index 3b32d612a45f..f712d449246d 100644 --- a/CRM/Activity/Form/Task/AddToTag.php +++ b/CRM/Activity/Form/Task/AddToTag.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Activity_Form_Task_AddToTag', 'formRule')); + $this->addFormRule(['CRM_Activity_Form_Task_AddToTag', 'formRule']); } /** @@ -81,7 +81,7 @@ public function addRules() { * @return array */ public static function formRule($form, $rule) { - $errors = array(); + $errors = []; if (empty($form['tag']) && empty($form['activity_taglist'])) { $errors['_qf_default'] = ts("Please select at least one tag."); } @@ -94,7 +94,7 @@ public static function formRule($form, $rule) { public function postProcess() { // Get the submitted values in an array. $params = $this->controller->exportValues($this->_name); - $activityTags = $tagList = array(); + $activityTags = $tagList = []; // check if contact tags exists if (!empty($params['tag'])) { @@ -131,22 +131,22 @@ public function postProcess() { // merge activity and taglist tags $allTags = CRM_Utils_Array::crmArrayMerge($activityTags, $tagList); - $this->_name = array(); + $this->_name = []; foreach ($allTags as $key => $dnc) { $this->_name[] = $this->_tags[$key]; list($total, $added, $notAdded) = CRM_Core_BAO_EntityTag::addEntitiesToTag($this->_activityHolderIds, $key, 'civicrm_activity', FALSE); - $status = array(ts('Activity tagged', array('count' => $added, 'plural' => '%count activities tagged'))); + $status = [ts('Activity tagged', ['count' => $added, 'plural' => '%count activities tagged'])]; if ($notAdded) { - $status[] = ts('1 activity already had this tag', array( + $status[] = ts('1 activity already had this tag', [ 'count' => $notAdded, 'plural' => '%count activities already had this tag', - )); + ]); } $status = ''; - CRM_Core_Session::setStatus($status, ts("Added Tag %1", array(1 => $this->_tags[$key])), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts("Added Tag %1", [1 => $this->_tags[$key]]), 'success', ['expires' => 0]); } } diff --git a/CRM/Activity/Form/Task/Batch.php b/CRM/Activity/Form/Task/Batch.php index 72a72febc180..8b3fc1674536 100644 --- a/CRM/Activity/Form/Task/Batch.php +++ b/CRM/Activity/Form/Task/Batch.php @@ -1,9 +1,9 @@ ts('Added By'), 'target_sort_name' => ts('With Contact')), + $readOnlyFields = array_merge(['sort_name' => ts('Added By'), 'target_sort_name' => ts('With Contact')], CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_autocomplete_options', TRUE, NULL, FALSE, 'name', TRUE @@ -78,6 +80,7 @@ public function preProcess() { if (!empty($contactDetails)) { foreach ($contactDetails as $key => $value) { $assignee = CRM_Activity_BAO_ActivityAssignment::retrieveAssigneeIdsByActivityId($key); + $assigneeContact = []; foreach ($assignee as $values) { $assigneeContact[] = CRM_Contact_BAO_Contact::displayName($values); } @@ -101,12 +104,12 @@ public function buildQuickForm() { CRM_Utils_System::setTitle($this->_title); $this->addDefaultButtons(ts('Save')); - $this->_fields = array(); + $this->_fields = []; $this->_fields = CRM_Core_BAO_UFGroup::getFields($ufGroupId, FALSE, CRM_Core_Action::VIEW); // remove file type field and then limit fields $suppressFields = FALSE; - $removehtmlTypes = array('File', 'Autocomplete-Select'); + $removehtmlTypes = ['File']; foreach ($this->_fields as $name => $field) { if (CRM_Core_BAO_CustomField::getKeyID($name) && in_array($this->_fields[$name]['html_type'], $removehtmlTypes) @@ -124,24 +127,24 @@ public function buildQuickForm() { $this->_fields = array_slice($this->_fields, 0, $this->_maxFields); - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'submit', 'name' => ts('Update Activities'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); $this->assign('profileTitle', $this->_title); $this->assign('componentIds', $this->_activityHolderIds); // Load all campaigns. if (array_key_exists('activity_campaign_id', $this->_fields)) { - $this->_componentCampaigns = array(); + $this->_componentCampaigns = []; CRM_Core_PseudoConstant::populate($this->_componentCampaigns, 'CRM_Activity_DAO_Activity', TRUE, 'campaign_id', 'id', @@ -153,7 +156,7 @@ public function buildQuickForm() { // It is possible to have fields that are required in CiviCRM not be required in the // profile. Overriding that here. Perhaps a better approach would be to // make them required in the schema & read that up through getFields functionality. - $requiredFields = array('activity_date_time'); + $requiredFields = ['activity_date_time']; foreach ($this->_activityHolderIds as $activityId) { $typeId = CRM_Core_DAO::getFieldValue("CRM_Activity_DAO_Activity", $activityId, 'activity_type_id'); @@ -187,7 +190,7 @@ public function buildQuickForm() { // $buttonName = $this->controller->getButtonName('submit'); if ($suppressFields) { - CRM_Core_Session::setStatus(ts("File or Autocomplete-Select type field(s) in the selected profile are not supported for Update multiple activities."), ts('Some Fields Excluded'), 'info'); + CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple activities."), ts('Some Fields Excluded'), 'info'); } $this->addDefaultButtons(ts('Update Activities')); @@ -201,7 +204,7 @@ public function setDefaultValues() { return; } - $defaults = array(); + $defaults = []; foreach ($this->_activityHolderIds as $activityId) { CRM_Core_BAO_UFGroup::setProfileDefaults(NULL, $this->_fields, $defaults, FALSE, $activityId, 'Activity'); } @@ -242,6 +245,9 @@ public function postProcess() { $activityId = civicrm_api3('activity', 'create', $value); // @todo this would be done by the api call above if the parames were passed through. + // @todo extract submit functions & + // extend CRM_Event_Form_Task_BatchTest::testSubmit with a data provider to test + // handling of custom data, specifically checkbox fields. if (!empty($value['custom']) && is_array($value['custom']) ) { diff --git a/CRM/Activity/Form/Task/Delete.php b/CRM/Activity/Form/Task/Delete.php index 6fee99f7dc4e..3bdcfc0916c4 100644 --- a/CRM/Activity/Form/Task/Delete.php +++ b/CRM/Activity/Form/Task/Delete.php @@ -1,9 +1,9 @@ '%count activities deleted.', 'count' => $deleted)); + $msg = ts('%count activity deleted.', ['plural' => '%count activities deleted.', 'count' => $deleted]); CRM_Core_Session::setStatus($msg, ts('Removed'), 'success'); } if ($failed) { - CRM_Core_Session::setStatus(ts('1 could not be deleted.', array('plural' => '%count could not be deleted.', 'count' => $failed)), ts('Error'), 'error'); + CRM_Core_Session::setStatus(ts('1 could not be deleted.', ['plural' => '%count could not be deleted.', 'count' => $failed]), ts('Error'), 'error'); } } diff --git a/CRM/Activity/Form/Task/Email.php b/CRM/Activity/Form/Task/Email.php index ad693bf5bb8a..e5338f33e51f 100644 --- a/CRM/Activity/Form/Task/Email.php +++ b/CRM/Activity/Form/Task/Email.php @@ -1,9 +1,9 @@ addEntityRef('unclosed_case_id', ts('Select Case'), array('entity' => 'Case'), TRUE); + $this->addEntityRef('unclosed_case_id', ts('Select Case'), ['entity' => 'Case'], TRUE); $this->addDefaultButtons(ts('Save')); } @@ -80,8 +82,8 @@ public function postProcess() { $caseId = $formparams['unclosed_case_id']; $filedActivities = 0; foreach ($this->_activityHolderIds as $key => $id) { - $targetContactValues = $defaults = array(); - $params = array('id' => $id); + $targetContactValues = $defaults = []; + $params = ['id' => $id]; CRM_Activity_BAO_Activity::retrieve($params, $defaults); if (CRM_Case_BAO_Case::checkPermission($id, 'File On Case', $defaults['activity_type_id'])) { @@ -92,13 +94,13 @@ public function postProcess() { $targetContactValues = implode(',', array_keys($targetContactValues)); } - $params = array( + $params = [ 'caseID' => $caseId, 'activityID' => $id, 'newSubject' => empty($defaults['subject']) ? '' : $defaults['subject'], 'targetContactIds' => $targetContactValues, 'mode' => 'file', - ); + ]; $error_msg = CRM_Activity_Page_AJAX::_convertToCaseActivity($params); if (empty($error_msg['error_msg'])) { @@ -109,16 +111,17 @@ public function postProcess() { } } else { - CRM_Core_Session::setStatus(ts('Not permitted to file activity %1 %2.', array( + CRM_Core_Session::setStatus( + ts('Not permitted to file activity %1 %2.', [ 1 => empty($defaults['subject']) ? '' : $defaults['subject'], 2 => $defaults['activity_date_time'], - )), + ]), ts("Error"), "error"); } } CRM_Core_Session::setStatus($filedActivities, ts("Filed Activities"), "success"); - CRM_Core_Session::setStatus("", ts('Total Selected Activities: %1', array(1 => count($this->_activityHolderIds))), "info"); + CRM_Core_Session::setStatus("", ts('Total Selected Activities: %1', [1 => count($this->_activityHolderIds)]), "info"); } } diff --git a/CRM/Activity/Form/Task/PickOption.php b/CRM/Activity/Form/Task/PickOption.php index 12cc7e4479d4..bef28a6b3b4a 100644 --- a/CRM/Activity/Form/Task/PickOption.php +++ b/CRM/Activity/Form/Task/PickOption.php @@ -1,9 +1,9 @@ _activityHolderIds) > $this->_maxActivities) { - CRM_Core_Session::setStatus(ts("The maximum number of Activities you can select to send an email is %1. You have selected %2. Please select fewer Activities from your search results and try again.", array( + CRM_Core_Session::setStatus(ts("The maximum number of Activities you can select to send an email is %1. You have selected %2. Please select fewer Activities from your search results and try again.", [ 1 => $this->_maxActivities, 2 => count($this->_activityHolderIds), - )), ts("Maximum Exceeded"), "error"); + ]), ts("Maximum Exceeded"), "error"); $validate = TRUE; } // then redirect @@ -92,7 +95,7 @@ public function buildQuickForm() { $this->addElement('checkbox', 'with_contact', ts('With Contact')); $this->addElement('checkbox', 'assigned_to', ts('Assigned to Contact')); $this->addElement('checkbox', 'created_by', ts('Created by')); - $this->setDefaults(array('with_contact' => 1)); + $this->setDefaults(['with_contact' => 1]); $this->addDefaultButtons(ts('Continue')); } @@ -100,7 +103,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Activity_Form_Task_PickOption', 'formRule')); + $this->addFormRule(['CRM_Activity_Form_Task_PickOption', 'formRule']); } /** @@ -117,7 +120,7 @@ public static function formRule($fields) { !isset($fields['assigned_to']) && !isset($fields['created_by']) ) { - return array('with_contact' => ts('You must select at least one email recipient type.')); + return ['with_contact' => ts('You must select at least one email recipient type.')]; } return TRUE; } @@ -129,9 +132,9 @@ public function postProcess() { // Clear any formRule errors from Email form in case they came back here via Cancel button $this->controller->resetPage('Email'); $params = $this->exportValues(); - $this->_contacts = array(); + $this->_contacts = []; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); // Get assignee contacts. diff --git a/CRM/Activity/Form/Task/PickProfile.php b/CRM/Activity/Form/Task/PickProfile.php index 13ad599e32be..1221a21c4abd 100644 --- a/CRM/Activity/Form/Task/PickProfile.php +++ b/CRM/Activity/Form/Task/PickProfile.php @@ -1,9 +1,9 @@ _activityHolderIds) > $this->_maxActivities) { - CRM_Core_Session::setStatus(ts("The maximum number of activities you can select for Update multiple activities is %1. You have selected %2. Please select fewer Activities from your search results and try again.", array( + CRM_Core_Session::setStatus(ts("The maximum number of activities you can select for Update multiple activities is %1. You have selected %2. Please select fewer Activities from your search results and try again.", [ 1 => $this->_maxActivities, 2 => count($this->_activityHolderIds), - )), ts('Maximum Exceeded'), 'error'); + ]), ts('Maximum Exceeded'), 'error'); $validate = TRUE; } @@ -85,11 +87,11 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $types = array('Activity'); + $types = ['Activity']; $profiles = CRM_Core_BAO_UFGroup::getProfiles($types, TRUE); $activityTypeIds = array_flip(CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name')); - $nonEditableActivityTypeIds = array( + $nonEditableActivityTypeIds = [ $activityTypeIds['Email'], $activityTypeIds['Bulk Email'], $activityTypeIds['Contribution'], @@ -99,7 +101,7 @@ public function buildQuickForm() { $activityTypeIds['Membership Renewal'], $activityTypeIds['Event Registration'], $activityTypeIds['Pledge Acknowledgment'], - ); + ]; $notEditable = FALSE; foreach ($this->_activityHolderIds as $activityId) { $typeId = CRM_Core_DAO::getFieldValue("CRM_Activity_DAO_Activity", $activityId, 'activity_type_id'); @@ -110,7 +112,7 @@ public function buildQuickForm() { } if (empty($profiles)) { - CRM_Core_Session::setStatus(ts("You will need to create a Profile containing the %1 fields you want to edit before you can use Update multiple activities. Navigate to Administer > Customize Data and Screens > Profiles to configure a Profile. Consult the online Administrator documentation for more information.", array(1 => $types[0])), ts("No Profile Configured"), "alert"); + CRM_Core_Session::setStatus(ts("You will need to create a Profile containing the %1 fields you want to edit before you can use Update multiple activities. Navigate to Administer > Customize Data and Screens > Profiles to configure a Profile. Consult the online Administrator documentation for more information.", [1 => $types[0]]), ts("No Profile Configured"), "alert"); CRM_Utils_System::redirect($this->_userContext); } elseif ($notEditable) { @@ -119,9 +121,9 @@ public function buildQuickForm() { } $ufGroupElement = $this->add('select', 'uf_group_id', ts('Select Profile'), - array( + [ '' => ts('- select profile -'), - ) + $profiles, TRUE + ] + $profiles, TRUE ); $this->addDefaultButtons(ts('Continue')); } @@ -130,7 +132,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Activity_Form_Task_PickProfile', 'formRule')); + $this->addFormRule(['CRM_Activity_Form_Task_PickProfile', 'formRule']); } /** diff --git a/CRM/Activity/Form/Task/Print.php b/CRM/Activity/Form/Task/Print.php index b61214a8c4d1..69f822bd74b7 100644 --- a/CRM/Activity/Form/Task/Print.php +++ b/CRM/Activity/Form/Task/Print.php @@ -1,9 +1,9 @@ addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'next', 'name' => ts('Print Activities'), - 'js' => array('onclick' => 'window.print()'), + 'js' => ['onclick' => 'window.print()'], 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'back', 'name' => ts('Done'), - ), - )); + ], + ]); } } diff --git a/CRM/Activity/Form/Task/RemoveFromTag.php b/CRM/Activity/Form/Task/RemoveFromTag.php index e1fecc0a3609..2590ba073d73 100644 --- a/CRM/Activity/Form/Task/RemoveFromTag.php +++ b/CRM/Activity/Form/Task/RemoveFromTag.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Activity_Form_Task_RemoveFromTag', 'formRule')); + $this->addFormRule(['CRM_Activity_Form_Task_RemoveFromTag', 'formRule']); } /** @@ -77,7 +77,7 @@ public function addRules() { * @return array */ public static function formRule($form, $rule) { - $errors = array(); + $errors = []; if (empty($form['tag']) && empty($form['activity_taglist'])) { $errors['_qf_default'] = "Please select atleast one tag."; } @@ -91,7 +91,7 @@ public function postProcess() { //get the submitted values in an array $params = $this->controller->exportValues($this->_name); - $activityTags = $tagList = array(); + $activityTags = $tagList = []; // check if contact tags exists if (!empty($params['tag'])) { @@ -120,27 +120,27 @@ public function postProcess() { // merge contact and taglist tags $allTags = CRM_Utils_Array::crmArrayMerge($activityTags, $tagList); - $this->_name = array(); + $this->_name = []; foreach ($allTags as $key => $dnc) { $this->_name[] = $this->_tags[$key]; list($total, $removed, $notRemoved) = CRM_Core_BAO_EntityTag::removeEntitiesFromTag($this->_activityHolderIds, $key, 'civicrm_activity', FALSE); - $status = array( - ts('%count activity un-tagged', array( + $status = [ + ts('%count activity un-tagged', [ 'count' => $removed, 'plural' => '%count activities un-tagged', - )), - ); + ]), + ]; if ($notRemoved) { - $status[] = ts('1 activity already did not have this tag', array( + $status[] = ts('1 activity already did not have this tag', [ 'count' => $notRemoved, 'plural' => '%count activities already did not have this tag', - )); + ]); } $status = ''; - CRM_Core_Session::setStatus($status, ts("Removed Tag %1", array(1 => $this->_tags[$key])), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts("Removed Tag %1", [1 => $this->_tags[$key]]), 'success', ['expires' => 0]); } } diff --git a/CRM/Activity/Form/Task/SMS.php b/CRM/Activity/Form/Task/SMS.php index b44723d4aea0..d19e1ea543fb 100644 --- a/CRM/Activity/Form/Task/SMS.php +++ b/CRM/Activity/Form/Task/SMS.php @@ -1,9 +1,9 @@ _activityHolderIds); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $query = " SELECT at.subject as subject, @@ -63,12 +63,12 @@ public function preProcess() { $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $rows[] = array( + $rows[] = [ 'subject' => $dao->subject, 'activity_type' => $dao->activity_type, 'activity_date' => $dao->activity_date, 'display_name' => $dao->display_name, - ); + ]; } $this->assign('rows', $rows); } @@ -77,13 +77,13 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'done', 'name' => ts('Done'), 'isDefault' => TRUE, - ), - )); + ], + ]); } } diff --git a/CRM/Activity/Import/Controller.php b/CRM/Activity/Import/Controller.php index 9ad060e9ad21..9edc8e66b4e3 100644 --- a/CRM/Activity/Import/Controller.php +++ b/CRM/Activity/Import/Controller.php @@ -1,9 +1,9 @@ addActions($config->uploadDir, array('uploadFile')); + $this->addActions($config->uploadDir, ['uploadFile']); } } diff --git a/CRM/Activity/Import/Field.php b/CRM/Activity/Import/Field.php index 029f7fd23b40..4f8471472ca1 100644 --- a/CRM/Activity/Import/Field.php +++ b/CRM/Activity/Import/Field.php @@ -1,9 +1,9 @@ createElement('radio', NULL, NULL, ts('Skip'), CRM_Import_Parser::DUPLICATE_SKIP ); @@ -67,11 +67,11 @@ public function buildQuickForm() { * Process the uploaded file. */ public function postProcess() { - $this->storeFormValues(array( + $this->storeFormValues([ 'onDuplicate', 'dateFormats', 'savedMapping', - )); + ]); $this->submitFileForMapping('CRM_Activity_Import_Parser_Activity'); } diff --git a/CRM/Activity/Import/Form/MapField.php b/CRM/Activity/Import/Form/MapField.php index 752e4a96078e..4e6f8e79c5b9 100644 --- a/CRM/Activity/Import/Form/MapField.php +++ b/CRM/Activity/Import/Form/MapField.php @@ -1,9 +1,9 @@ assign('rowDisplayCount', 2); } - $highlightedFields = array(); - $requiredFields = array( + $highlightedFields = []; + $requiredFields = [ 'activity_date_time', 'activity_type_id', 'activity_label', 'target_contact_id', 'activity_subject', - ); + ]; foreach ($requiredFields as $val) { $highlightedFields[] = $val; } @@ -78,6 +77,8 @@ public function preProcess() { /** * Build the form object. + * + * @throws \CiviCRM_API3_Exception */ public function buildQuickForm() { // To save the current mappings. @@ -91,20 +92,14 @@ public function buildQuickForm() { $savedMapping = $this->get('savedMapping'); // Mapping is to be loaded from database. - list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingRelation) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping); - - // Get loaded Mapping Fields. - $mappingName = CRM_Utils_Array::value('1', $mappingName); - $mappingContactType = CRM_Utils_Array::value('1', $mappingContactType); - $mappingLocation = CRM_Utils_Array::value('1', $mappingLocation); - $mappingPhoneType = CRM_Utils_Array::value('1', $mappingPhoneType); - $mappingRelation = CRM_Utils_Array::value('1', $mappingRelation); + // Get an array of the name values for mapping fields associated with this mapping_id. + $mappingName = CRM_Core_BAO_Mapping::getMappingFieldValues($savedMapping, 'name'); $this->assign('loadedMapping', $savedMapping); $this->set('loadedMapping', $savedMapping); - $params = array('id' => $savedMapping); - $temp = array(); + $params = ['id' => $savedMapping]; + $temp = []; $mappingDetails = CRM_Core_BAO_Mapping::retrieve($params, $temp); $this->assign('savedName', $mappingDetails->name); @@ -117,13 +112,13 @@ public function buildQuickForm() { $this->add('text', 'saveMappingDesc', ts('Description')); } - $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, array('onclick' => "showSaveDetails(this)")); + $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, ['onclick' => "showSaveDetails(this)"]); - $this->addFormRule(array('CRM_Activity_Import_Form_MapField', 'formRule')); + $this->addFormRule(['CRM_Activity_Import_Form_MapField', 'formRule']); //-------- end of saved mapping stuff --------- - $defaults = array(); + $defaults = []; $mapperKeys = array_keys($this->_mapperFields); $hasHeaders = !empty($this->_columnHeaders); @@ -148,7 +143,7 @@ public function buildQuickForm() { $warning = 0; for ($i = 0; $i < $this->_columnCount; $i++) { - $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', array(1 => $i)), NULL); + $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL); $jsSet = FALSE; if ($this->get('savedMapping')) { if (isset($mappingName[$i])) { @@ -165,15 +160,15 @@ public function buildQuickForm() { } $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n"; - $defaults["mapper[$i]"] = array( + $defaults["mapper[$i]"] = [ $mappingHeader[0], (isset($locationId)) ? $locationId : "", (isset($phoneType)) ? $phoneType : "", - ); + ]; $jsSet = TRUE; } else { - $defaults["mapper[$i]"] = array(); + $defaults["mapper[$i]"] = []; } if (!$jsSet) { for ($k = 1; $k < 4; $k++) { @@ -186,14 +181,10 @@ public function buildQuickForm() { $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n"; if ($hasHeaders) { - $defaults["mapper[$i]"] = array( - $this->defaultFromHeader($this->_columnHeaders[$i], - $headerPatterns - ), - ); + $defaults["mapper[$i]"] = [$this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns)]; } else { - $defaults["mapper[$i]"] = array($this->defaultFromData($dataPatterns, $i)); + $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i)]; } } // End of load mapping. @@ -202,23 +193,23 @@ public function buildQuickForm() { $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n"; if ($hasHeaders) { // Infer the default from the skipped headers if we have them - $defaults["mapper[$i]"] = array( - $this->defaultFromHeader($this->_columnHeaders[$i], - $headerPatterns - ), + $defaults["mapper[$i]"] = [ + $this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns), 0, - ); + ]; } else { // Otherwise guess the default from the form of the data - $defaults["mapper[$i]"] = array( - $this->defaultFromData($dataPatterns, $i), - 0, - ); + $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i), 0]; } } - $sel->setOptions(array($sel1, $sel2, (isset($sel3)) ? $sel3 : "", (isset($sel4)) ? $sel4 : "")); + $sel->setOptions([ + $sel1, + $sel2, + (isset($sel3)) ? $sel3 : "", + (isset($sel4)) ? $sel4 : "", + ]); } $js .= "\n"; $this->assign('initHideBoxes', $js); @@ -240,22 +231,22 @@ public function buildQuickForm() { $this->setDefaults($defaults); - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'back', 'name' => ts('Previous'), - ), - array( + ], + [ 'type' => 'next', 'name' => ts('Continue'), 'spacing' => '          ', 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - ) + ], + ] ); } @@ -269,28 +260,28 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields) { - $errors = array(); + $errors = []; // define so we avoid notices below $errors['_qf_default'] = ''; $fieldMessage = NULL; if (!array_key_exists('savedMapping', $fields)) { - $importKeys = array(); + $importKeys = []; foreach ($fields['mapper'] as $mapperPart) { $importKeys[] = $mapperPart[0]; } // FIXME: should use the schema titles, not redeclare them - $requiredFields = array( + $requiredFields = [ 'target_contact_id' => ts('Contact ID'), 'activity_date_time' => ts('Activity Date'), 'activity_subject' => ts('Activity Subject'), 'activity_type_id' => ts('Activity Type ID'), - ); + ]; - $params = array( + $params = [ 'used' => 'Unsupervised', 'contact_type' => 'Individual', - ); + ]; list($ruleFields, $threshold) = CRM_Dedupe_BAO_RuleGroup::dedupeRuleFieldsWeight($params); $weightSum = 0; foreach ($importKeys as $key => $val) { @@ -310,7 +301,7 @@ public static function formRule($fields) { else { $errors['_qf_default'] .= ts('Missing required contact matching fields.') . $fieldMessage . ' ' - . ts('(Sum of all weights should be greater than or equal to threshold: %1).', array(1 => $threshold)) + . ts('(Sum of all weights should be greater than or equal to threshold: %1).', [1 => $threshold]) . '
'; } } @@ -320,14 +311,14 @@ public static function formRule($fields) { } else { $errors['_qf_default'] .= ts('Missing required field: Provide %1 or %2', - array( + [ 1 => $title, 2 => 'Activity Type Label', - )) . '
'; + ]) . '
'; } } else { - $errors['_qf_default'] .= ts('Missing required field: %1', array(1 => $title)) . '
'; + $errors['_qf_default'] .= ts('Missing required field: %1', [1 => $title]) . '
'; } } } @@ -339,8 +330,7 @@ public static function formRule($fields) { $errors['saveMappingName'] = ts('Name is required to save Import Mapping'); } else { - $mappingTypeId = CRM_Core_OptionGroup::getValue('mapping_type', 'Import Activity', 'name'); - if (CRM_Core_BAO_Mapping::checkMapping($nameField, $mappingTypeId)) { + if (CRM_Core_BAO_Mapping::checkMapping($nameField, CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Activity'))) { $errors['saveMappingName'] = ts('Duplicate Import Mapping Name'); } } @@ -379,12 +369,12 @@ public function postProcess() { $seperator = $this->controller->exportValue('DataSource', 'fieldSeparator'); $skipColumnHeader = $this->controller->exportValue('DataSource', 'skipColumnHeader'); - $mapperKeys = array(); - $mapper = array(); + $mapperKeys = []; + $mapper = []; $mapperKeys = $this->controller->exportValue($this->_name, 'mapper'); - $mapperKeysMain = array(); - $mapperLocType = array(); - $mapperPhoneType = array(); + $mapperKeysMain = []; + $mapperLocType = []; + $mapperPhoneType = []; for ($i = 0; $i < $this->_columnCount; $i++) { $mapper[$i] = $this->_mapperFields[$mapperKeys[$i][0]]; @@ -418,7 +408,7 @@ public function postProcess() { $mappingFields->mapping_id = $params['mappingId']; $mappingFields->find(); - $mappingFieldsId = array(); + $mappingFieldsId = []; while ($mappingFields->fetch()) { if ($mappingFields->id) { $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id; @@ -438,14 +428,11 @@ public function postProcess() { // Saving Mapping Details and Records. if (!empty($params['saveMapping'])) { - $mappingParams = array( + $mappingParams = [ 'name' => $params['saveMappingName'], 'description' => $params['saveMappingDesc'], - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Import Activity', - 'name' - ), - ); + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Activity'), + ]; $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams); for ($i = 0; $i < $this->_columnCount; $i++) { diff --git a/CRM/Activity/Import/Form/Preview.php b/CRM/Activity/Import/Form/Preview.php index b74b0d6f133d..6a2352d06213 100644 --- a/CRM/Activity/Import/Form/Preview.php +++ b/CRM/Activity/Import/Form/Preview.php @@ -1,9 +1,9 @@ set('downloadMismatchRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams)); } - $properties = array( + $properties = [ 'mapper', 'dataValues', 'columnCount', @@ -93,7 +93,8 @@ public function preProcess() { 'downloadErrorRecordsUrl', 'downloadConflictRecordsUrl', 'downloadMismatchRecordsUrl', - ); + ]; + $this->setStatusUrl(); foreach ($properties as $property) { $this->assign($property, $this->get($property)); @@ -114,9 +115,9 @@ public function postProcess() { $onDuplicate = $this->get('onDuplicate'); $mapper = $this->controller->exportValue('MapField', 'mapper'); - $mapperKeys = array(); - $mapperLocType = array(); - $mapperPhoneType = array(); + $mapperKeys = []; + $mapperLocType = []; + $mapperPhoneType = []; foreach ($mapper as $key => $value) { $mapperKeys[$key] = $mapper[$key][0]; @@ -141,7 +142,7 @@ public function postProcess() { $mapFields = $this->get('fields'); foreach ($mapper as $key => $value) { - $header = array(); + $header = []; if (isset($mapFields[$mapper[$key][0]])) { $header[] = $mapFields[$mapper[$key][0]]; } @@ -151,7 +152,9 @@ public function postProcess() { $mapperFields, $skipColumnHeader, CRM_Import_Parser::MODE_IMPORT, - $onDuplicate + $onDuplicate, + $this->get('statusID'), + $this->get('totalRowCount') ); // add all the necessary variables to the form @@ -161,7 +164,7 @@ public function postProcess() { $errorStack = CRM_Core_Error::singleton(); $errors = $errorStack->getErrors(); - $errorMessage = array(); + $errorMessage = []; if (is_array($errors)) { foreach ($errors as $key => $value) { diff --git a/CRM/Activity/Import/Form/Summary.php b/CRM/Activity/Import/Form/Summary.php index 83f360a7a05e..5e263c5edbc7 100644 --- a/CRM/Activity/Import/Form/Summary.php +++ b/CRM/Activity/Import/Form/Summary.php @@ -1,9 +1,9 @@ assign('dupeActionString', $dupeActionString); - $properties = array( + $properties = [ 'totalRowCount', 'validRowCount', 'invalidRowCount', @@ -101,7 +101,7 @@ public function preProcess() { 'downloadMismatchRecordsUrl', 'groupAdditions', 'unMatchCount', - ); + ]; foreach ($properties as $property) { $this->assign($property, $this->get($property)); } diff --git a/CRM/Activity/Import/Parser.php b/CRM/Activity/Import/Parser.php index 0c0d35161d63..67198fee6874 100644 --- a/CRM/Activity/Import/Parser.php +++ b/CRM/Activity/Import/Parser.php @@ -1,9 +1,9 @@ _invalidRowCount = $this->_validCount = 0; $this->_totalCount = $this->_conflictCount = 0; - $this->_errors = array(); - $this->_warnings = array(); - $this->_conflicts = array(); + $this->_errors = []; + $this->_warnings = []; + $this->_conflicts = []; $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2); if ($mode == self::MODE_MAPFIELD) { - $this->_rows = array(); + $this->_rows = []; } else { $this->_activeFieldCount = count($this->_activeFields); } + if ($statusID) { + $this->progressImport($statusID); + $startTimestamp = $currTimestamp = $prevTimestamp = time(); + } while (!feof($fd)) { $this->_lineCount++; @@ -148,6 +159,9 @@ public function run( } elseif ($mode == self::MODE_IMPORT) { $returnCode = $this->import($onDuplicate, $values); + if ($statusID && (($this->_lineCount % 50) == 0)) { + $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount); + } } else { $returnCode = self::ERROR; @@ -171,14 +185,12 @@ public function run( if ($returnCode & self::ERROR) { $this->_invalidRowCount++; - if ($this->_invalidRowCount < $this->_maxErrorCount) { - $recordNumber = $this->_lineCount; - if ($this->_haveColumnHeader) { - $recordNumber--; - } - array_unshift($values, $recordNumber); - $this->_errors[] = $values; + $recordNumber = $this->_lineCount; + if ($this->_haveColumnHeader) { + $recordNumber--; } + array_unshift($values, $recordNumber); + $this->_errors[] = $values; } if ($returnCode & self::CONFLICT) { @@ -233,30 +245,24 @@ public function run( } if ($this->_invalidRowCount) { // removed view url for invlaid contacts - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), + $headers = array_merge( + [ts('Line Number'), ts('Reason')], $customHeaders ); $this->_errorFileName = self::errorFileName(self::ERROR); self::exportCSV($this->_errorFileName, $headers, $this->_errors); } if ($this->_conflictCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), + $headers = array_merge( + [ts('Line Number'), ts('Reason')], $customHeaders ); $this->_conflictFileName = self::errorFileName(self::CONFLICT); self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts); } if ($this->_duplicateCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('View Activity History URL'), - ), + $headers = array_merge( + [ts('Line Number'), ts('View Activity History URL')], $customHeaders ); @@ -291,7 +297,7 @@ public function setActiveFields($fieldKeys) { * (reference ) associative array of name/value pairs */ public function &getActiveFieldParams() { - $params = array(); + $params = []; for ($i = 0; $i < $this->_activeFieldCount; $i++) { if (isset($this->_activeFields[$i]->_value) && !isset($params[$this->_activeFields[$i]->_name]) @@ -376,7 +382,7 @@ public function set($store, $mode = self::MODE_SUMMARY) { * @param array $data */ public static function exportCSV($fileName, $header, $data) { - $output = array(); + $output = []; $fd = fopen($fileName, 'w'); foreach ($header as $key => $value) { diff --git a/CRM/Activity/Import/Parser/Activity.php b/CRM/Activity/Import/Parser/Activity.php index 4e41cf675b20..de829240c3d7 100644 --- a/CRM/Activity/Import/Parser/Activity.php +++ b/CRM/Activity/Import/Parser/Activity.php @@ -1,9 +1,9 @@ array( + $fields = array_merge($fields, [ + 'source_contact_id' => [ 'title' => ts('Source Contact'), 'headerPattern' => '/Source.Contact?/i', - ), - 'activity_label' => array( + ], + 'activity_label' => [ 'title' => ts('Activity Type Label'), 'headerPattern' => '/(activity.)?type label?/i', - ), - )); + ], + ]); foreach ($fields as $name => $field) { $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT); @@ -96,7 +96,7 @@ public function init() { $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']); } - $this->_newActivity = array(); + $this->_newActivity = []; $this->setActiveFields($this->_mapperKeys); @@ -258,7 +258,7 @@ public function import($onDuplicate, &$values) { $params = &$this->getActiveFieldParams(); $activityLabel = array_search('activity_label', $this->_mapperKeys); if ($activityLabel) { - $params = array_merge($params, array('activity_label' => $values[$activityLabel])); + $params = array_merge($params, ['activity_label' => $values[$activityLabel]]); } // For date-Formats. $session = CRM_Core_Session::singleton(); @@ -332,10 +332,10 @@ public function import($onDuplicate, &$values) { } else { // Using new Dedupe rule. - $ruleParams = array( + $ruleParams = [ 'contact_type' => 'Individual', 'used' => 'Unsupervised', - ); + ]; $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams); $disp = NULL; diff --git a/CRM/Activity/Page/AJAX.php b/CRM/Activity/Page/AJAX.php index b7f4e14624ee..5688dd5093fd 100644 --- a/CRM/Activity/Page/AJAX.php +++ b/CRM/Activity/Page/AJAX.php @@ -1,9 +1,9 @@ 'Integer', 'status_id' => 'Integer', 'activity_deleted' => 'Boolean', 'activity_type_id' => 'Integer', - 'activity_date_low' => 'Date', - 'activity_date_high' => 'Date', - ); + // "Date" validation fails because it expects only numbers with no hyphens + 'activity_date_low' => 'Alphanumeric', + 'activity_date_high' => 'Alphanumeric', + ]; $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams(); - $params += CRM_Core_Page_AJAX::validateParams(array(), $optionalParameters); + $params += CRM_Core_Page_AJAX::validateParams([], $optionalParameters); // get the activities related to given case $activities = CRM_Case_BAO_Case::getCaseActivity($caseID, $params, $contactID, $context, $userID); @@ -65,17 +67,17 @@ public static function getCaseGlobalRelationships() { $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams(); // get the activities related to given case - $globalGroupInfo = array(); + $globalGroupInfo = []; // get the total row count CRM_Case_BAO_Case::getGlobalContacts($globalGroupInfo, NULL, FALSE, TRUE, NULL, NULL); // limit the rows $relGlobal = CRM_Case_BAO_Case::getGlobalContacts($globalGroupInfo, $params['sortBy'], $showLinks = TRUE, FALSE, $params['offset'], $params['rp']); - $relationships = array(); + $relationships = []; // after sort we can update username fields to be a url foreach ($relGlobal as $key => $value) { - $relationship = array(); + $relationship = []; $relationship['sort_name'] = $value['sort_name']; $relationship['phone'] = $value['phone']; $relationship['email'] = $value['email']; @@ -83,7 +85,7 @@ public static function getCaseGlobalRelationships() { array_push($relationships, $relationship); } - $globalRelationshipsDT = array(); + $globalRelationshipsDT = []; $globalRelationshipsDT['data'] = $relationships; $globalRelationshipsDT['recordsTotal'] = count($relationships); $globalRelationshipsDT['recordsFiltered'] = count($relationships); @@ -107,7 +109,7 @@ public static function getCaseClientRelationships() { // Now build 'Other Relationships' array by removing relationships that are already listed under Case Roles // so they don't show up twice. - $clientRelationships = array(); + $clientRelationships = []; foreach ($relClient as $r) { if (!array_key_exists($r['id'], $caseRelationships)) { $clientRelationships[] = $r; @@ -121,10 +123,10 @@ public static function getCaseClientRelationships() { $sort_type = "SORT_" . strtoupper($params['_raw_values']['order'][0]); array_multisort($sortArray, constant($sort_type), $clientRelationships); - $relationships = array(); + $relationships = []; // after sort we can update username fields to be a url foreach ($clientRelationships as $key => $value) { - $relationship = array(); + $relationship = []; $relationship['relation'] = $value['relation']; $relationship['name'] = '' . $clientRelationships[$key]['name'] . ''; @@ -134,7 +136,7 @@ public static function getCaseClientRelationships() { array_push($relationships, $relationship); } - $clientRelationshipsDT = array(); + $clientRelationshipsDT = []; $clientRelationshipsDT['data'] = $relationships; $clientRelationshipsDT['recordsTotal'] = count($relationships); $clientRelationshipsDT['recordsFiltered'] = count($relationships); @@ -142,7 +144,6 @@ public static function getCaseClientRelationships() { CRM_Utils_JSON::output($clientRelationshipsDT); } - public static function getCaseRoles() { $caseID = CRM_Utils_Type::escape($_GET['caseID'], 'Integer'); $contactID = CRM_Utils_Type::escape($_GET['cid'], 'Integer'); @@ -160,7 +161,7 @@ public static function getCaseRoles() { foreach ($caseRelationships as $key => $value) { // This role has been filled - unset($caseRoles[$value['relation_type']]); + unset($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']]); // mark original case relationships record to use on setting edit links below $caseRelationships[$key]['source'] = 'caseRel'; } @@ -172,7 +173,7 @@ public static function getCaseRoles() { // CRM-14466 added cid to the non-client array to avoid php notice foreach ($caseRoles as $id => $value) { if ($id != "client") { - $rel = array(); + $rel = []; $rel['relation'] = $value; $rel['relation_type'] = $id; $rel['name'] = '(not assigned)'; @@ -183,8 +184,8 @@ public static function getCaseRoles() { } else { foreach ($value as $clientRole) { - $relClient = array(); - $relClient['relation'] = 'Client'; + $relClient = []; + $relClient['relation'] = ts('Client'); $relClient['name'] = $clientRole['sort_name']; $relClient['phone'] = $clientRole['phone']; $relClient['email'] = $clientRole['email']; @@ -202,13 +203,13 @@ public static function getCaseRoles() { $sort_type = "SORT_" . strtoupper($params['_raw_values']['order'][0]); array_multisort($sortArray, constant($sort_type), $caseRelationships); - $relationships = array(); + $relationships = []; // set user name, email and edit columns links foreach ($caseRelationships as $key => &$row) { $typeLabel = $row['relation']; // Add "
(Case Manager)" to label - if ($row['relation_type'] == $managerRoleId) { + if (!empty($row['relation_type']) && !empty($row['relationship_direction']) && $row['relation_type'] . '_' . $row['relationship_direction'] == $managerRoleId) { $row['relation'] .= '
' . '(' . ts('Case Manager') . ')'; } // view user links @@ -227,16 +228,16 @@ public static function getCaseRoles() { $contactType = $contactType == 'Contact' ? '' : $contactType; switch ($row['source']) { case 'caseRel': - $row['actions'] = '' . + $row['actions'] = '' . '' . '' . - '' . + '' . '' . ''; break; case 'caseRoles': - $row['actions'] = '' . + $row['actions'] = '' . '' . ''; break; @@ -251,7 +252,7 @@ public static function getCaseRoles() { } $params['total'] = count($relationships); - $caseRelationshipsDT = array(); + $caseRelationshipsDT = []; $caseRelationshipsDT['data'] = $relationships; $caseRelationshipsDT['recordsTotal'] = $params['total']; $caseRelationshipsDT['recordsFiltered'] = $params['total']; @@ -261,8 +262,8 @@ public static function getCaseRoles() { } public static function convertToCaseActivity() { - $params = array('caseID', 'activityID', 'contactID', 'newSubject', 'targetContactIds', 'mode'); - $vals = array(); + $params = ['caseID', 'activityID', 'contactID', 'newSubject', 'targetContactIds', 'mode']; + $vals = []; foreach ($params as $param) { $vals[$param] = CRM_Utils_Array::value($param, $_POST); } @@ -277,19 +278,19 @@ public static function convertToCaseActivity() { */ public static function _convertToCaseActivity($params) { if (!$params['activityID'] || !$params['caseID']) { - return (array('error_msg' => 'required params missing.')); + return (['error_msg' => 'required params missing.']); } $otherActivity = new CRM_Activity_DAO_Activity(); $otherActivity->id = $params['activityID']; if (!$otherActivity->find(TRUE)) { - return (array('error_msg' => 'activity record is missing.')); + return (['error_msg' => 'activity record is missing.']); } $actDateTime = CRM_Utils_Date::isoToMysql($otherActivity->activity_date_time); // Create new activity record. $mainActivity = new CRM_Activity_DAO_Activity(); - $mainActVals = array(); + $mainActVals = []; CRM_Core_DAO::storeValues($otherActivity, $mainActVals); // Get new activity subject. @@ -302,20 +303,19 @@ public static function _convertToCaseActivity($params) { $mainActivity->activity_date_time = $actDateTime; // Make sure this is current revision. $mainActivity->is_current_revision = TRUE; - // Drop all relations. - $mainActivity->parent_id = $mainActivity->original_id = NULL; + $mainActivity->original_id = $otherActivity->id; + $otherActivity->is_current_revision = FALSE; $mainActivity->save(); $mainActivityId = $mainActivity->id; CRM_Activity_BAO_Activity::logActivityAction($mainActivity); - $mainActivity->free(); // Mark previous activity as deleted. If it was a non-case activity // then just change the subject. - if (in_array($params['mode'], array( + if (in_array($params['mode'], [ 'move', 'file', - ))) { + ])) { $caseActivity = new CRM_Case_DAO_CaseActivity(); $caseActivity->case_id = $params['caseID']; $caseActivity->activity_id = $otherActivity->id; @@ -323,55 +323,52 @@ public static function _convertToCaseActivity($params) { $otherActivity->is_deleted = 1; } else { - $otherActivity->subject = ts('(Filed on case %1)', array( + $otherActivity->subject = ts('(Filed on case %1)', [ 1 => $params['caseID'], - )) . ' ' . $otherActivity->subject; + ]) . ' ' . $otherActivity->subject; } - $otherActivity->activity_date_time = $actDateTime; $otherActivity->save(); - $caseActivity->free(); } - $otherActivity->free(); - $targetContacts = array(); + $targetContacts = []; if (!empty($params['targetContactIds'])) { $targetContacts = array_unique(explode(',', $params['targetContactIds'])); } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $sourceContactID = CRM_Activity_BAO_Activity::getSourceContactID($params['activityID']); - $src_params = array( + $src_params = [ 'activity_id' => $mainActivityId, 'contact_id' => $sourceContactID, 'record_type_id' => $sourceID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($src_params); foreach ($targetContacts as $key => $value) { - $targ_params = array( + $targ_params = [ 'activity_id' => $mainActivityId, 'contact_id' => $value, 'record_type_id' => $targetID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($targ_params); } - // typically this will be empty, since assignees on another case may be completely different - $assigneeContacts = array(); + //CRM-21114 retrieve assignee contacts from original case; allow overriding from params + $assigneeContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($params['activityID'], $assigneeID); if (!empty($params['assigneeContactIds'])) { $assigneeContacts = array_unique(explode(',', $params['assigneeContactIds'])); } foreach ($assigneeContacts as $key => $value) { - $assigneeParams = array( + $assigneeParams = [ 'activity_id' => $mainActivityId, 'contact_id' => $value, 'record_type_id' => $assigneeID, - ); + ]; CRM_Activity_BAO_ActivityContact::create($assigneeParams); } @@ -381,12 +378,12 @@ public static function _convertToCaseActivity($params) { $caseActivity->activity_id = $mainActivityId; $caseActivity->save(); $error_msg = $caseActivity->_lastError; - $caseActivity->free(); $params['mainActivityId'] = $mainActivityId; CRM_Activity_BAO_Activity::copyExtendedActivityData($params); + CRM_Utils_Hook::post('create', 'CaseActivity', $caseActivity->id, $caseActivity); - return (array('error_msg' => $error_msg, 'newId' => $mainActivity->id)); + return (['error_msg' => $error_msg, 'newId' => $mainActivity->id]); } /** @@ -395,15 +392,19 @@ public static function _convertToCaseActivity($params) { * @return array */ public static function getContactActivity() { - $requiredParameters = array( + $requiredParameters = [ 'cid' => 'Integer', - ); + ]; - $optionalParameters = array( + $optionalParameters = [ 'context' => 'String', 'activity_type_id' => 'Integer', 'activity_type_exclude_id' => 'Integer', - ); + 'activity_status_id' => 'String', + 'activity_date_time_relative' => 'String', + 'activity_date_time_low' => 'String', + 'activity_date_time_high' => 'String', + ]; $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams(); $params += CRM_Core_Page_AJAX::validateParams($requiredParameters, $optionalParameters); @@ -416,32 +417,40 @@ public static function getContactActivity() { // get the contact activities $activities = CRM_Activity_BAO_Activity::getContactActivitySelector($params); - if (!empty($_GET['is_unit_test'])) { - return $activities; - } - foreach ($activities['data'] as $key => $value) { // Check if recurring activity. if (!empty($value['is_recurring_activity'])) { $repeat = $value['is_recurring_activity']; - $activities['data'][$key]['activity_type'] .= '
' . ts('Repeating (%1 of %2)', array(1 => $repeat[0], 2 => $repeat[1])) . ''; + $activities['data'][$key]['activity_type'] .= '
' . ts('Repeating (%1 of %2)', [1 => $repeat[0], 2 => $repeat[1]]) . ''; } } // store the activity filter preference CRM-11761 - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); - if ($userID) { - $activityFilter = array( - 'activity_type_filter_id' => empty($params['activity_type_id']) ? '' : CRM_Utils_Type::escape($params['activity_type_id'], 'Integer'), - 'activity_type_exclude_filter_id' => empty($params['activity_type_exclude_id']) ? '' : CRM_Utils_Type::escape($params['activity_type_exclude_id'], 'Integer'), - ); - - /** - * @var \Civi\Core\SettingsBag $cSettings - */ - $cSettings = Civi::service('settings_manager')->getBagByContact(CRM_Core_Config::domainID(), $userID); - $cSettings->set('activity_tab_filter', $activityFilter); + if (Civi::settings()->get('preserve_activity_tab_filter') && ($userID = CRM_Core_Session::getLoggedInContactID())) { + unset($optionalParameters['context']); + foreach ($optionalParameters as $searchField => $dataType) { + $formSearchField = $searchField; + if ($searchField == 'activity_type_id') { + $formSearchField = 'activity_type_filter_id'; + } + elseif ($searchField == 'activity_type_exclude_id') { + $formSearchField = 'activity_type_exclude_filter_id'; + } + if (!empty($params[$searchField])) { + $activityFilter[$formSearchField] = $params[$searchField]; + if (in_array($searchField, ['activity_date_time_low', 'activity_date_time_high'])) { + $activityFilter['activity_date_time_relative'] = 0; + } + elseif ($searchField == 'activity_status_id') { + $activityFilter['status_id'] = explode(',', $activityFilter[$searchField]); + } + } + } + + Civi::contactSettings()->set('activity_tab_filter', $activityFilter); + } + if (!empty($_GET['is_unit_test'])) { + return [$activities, $activityFilter]; } CRM_Utils_JSON::output($activities); diff --git a/CRM/Activity/Page/Tab.php b/CRM/Activity/Page/Tab.php index 84c9f096fa7f..4415618a7e2f 100644 --- a/CRM/Activity/Page/Tab.php +++ b/CRM/Activity/Page/Tab.php @@ -1,9 +1,9 @@ assign('context', $context); $this->_id = CRM_Utils_Request::retrieve('id', 'Integer', $this); @@ -73,25 +73,25 @@ public function edit() { $activityTypeId = CRM_Utils_Request::retrieve('atype', 'Positive', $this); // Email and Create Letter activities use a different form class - $emailTypeValue = CRM_Core_OptionGroup::getValue('activity_type', - 'Email', - 'name' + $emailTypeValue = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', + 'activity_type_id', + 'Email' ); - $letterTypeValue = CRM_Core_OptionGroup::getValue('activity_type', - 'Print PDF Letter', - 'name' + $letterTypeValue = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', + 'activity_type_id', + 'Print PDF Letter' ); switch ($activityTypeId) { case $emailTypeValue: $wrapper = new CRM_Utils_Wrapper(); - $arguments = array('attachUpload' => 1); + $arguments = ['attachUpload' => 1]; return $wrapper->run('CRM_Contact_Form_Task_Email', ts('Email a Contact'), $arguments); case $letterTypeValue: $wrapper = new CRM_Utils_Wrapper(); - $arguments = array('attachUpload' => 1); + $arguments = ['attachUpload' => 1]; return $wrapper->run('CRM_Contact_Form_Task_PDF', ts('Create PDF Letter'), $arguments); default: @@ -131,6 +131,7 @@ public function preProcess() { $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); $this->assign('action', $this->_action); + $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions()); // also create the form element for the activity links box $controller = new CRM_Core_Controller_Simple( @@ -159,14 +160,14 @@ public function delete() { * Perform actions and display for activities. */ public function run() { - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this); $action = CRM_Utils_Request::retrieve('action', 'String', $this); $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); // Do check for view/edit operation. if ($this->_id && - in_array($action, array(CRM_Core_Action::UPDATE, CRM_Core_Action::VIEW)) + in_array($action, [CRM_Core_Action::UPDATE, CRM_Core_Action::VIEW]) ) { if (!CRM_Activity_BAO_Activity::checkPermission($this->_id, $action)) { CRM_Core_Error::fatal(ts('You are not authorized to access this page.')); @@ -189,20 +190,20 @@ public function run() { $activityTypeId = CRM_Utils_Request::retrieve('atype', 'Positive', $this); // Email and Create Letter activities use a different form class - $emailTypeValue = CRM_Core_OptionGroup::getValue('activity_type', - 'Email', - 'name' + $emailTypeValue = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', + 'activity_type_id', + 'Email' ); - $letterTypeValue = CRM_Core_OptionGroup::getValue('activity_type', - 'Print PDF Letter', - 'name' + $letterTypeValue = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', + 'activity_type_id', + 'Print PDF Letter' ); - if (in_array($activityTypeId, array( + if (in_array($activityTypeId, [ $emailTypeValue, $letterTypeValue, - ))) { + ])) { return; } } diff --git a/CRM/Activity/Page/UserDashboard.php b/CRM/Activity/Page/UserDashboard.php index ad32029a42df..d16a23d32ef2 100644 --- a/CRM/Activity/Page/UserDashboard.php +++ b/CRM/Activity/Page/UserDashboard.php @@ -1,9 +1,9 @@ set('context', 'user'); $controller->set('cid', $this->_contactId); // Limit to status "Scheduled" and "Available" - $controller->set('status', array('IN' => array(1, 7))); + $controller->set('status', ['IN' => [1, 7]]); $controller->set('activity_role', 2); $controller->set('force', 1); $controller->process(); diff --git a/CRM/Activity/Selector/Activity.php b/CRM/Activity/Selector/Activity.php index 69972ea1102c..6fef52aaff35 100644 --- a/CRM/Activity/Selector/Activity.php +++ b/CRM/Activity/Selector/Activity.php @@ -1,9 +1,9 @@ array( + VIEW => [ 'name' => ts('View'), 'url' => $url, 'qs' => $qsView, 'title' => ts('View Activity'), - ), - ); + ], + ]; } if ($showUpdate) { @@ -231,15 +235,15 @@ public static function actionLinks( $updateUrl = 'civicrm/activity/pdf/add'; } if (CRM_Activity_BAO_Activity::checkPermission($activityId, CRM_Core_Action::UPDATE)) { - $actionLinks += array( + $actionLinks += [ CRM_Core_Action:: - UPDATE => array( + UPDATE => [ 'name' => ts('Edit'), 'url' => $updateUrl, 'qs' => $qsUpdate, 'title' => ts('Update Activity'), - ), - ); + ], + ]; } } @@ -247,42 +251,42 @@ public static function actionLinks( $activityTypeName && CRM_Case_BAO_Case::checkPermission($activityId, 'File On Case', $activityTypeId) ) { - $actionLinks += array( + $actionLinks += [ CRM_Core_Action:: - ADD => array( + ADD => [ 'name' => ts('File on Case'), 'url' => '#', 'extra' => 'onclick="javascript:fileOnCase( \'file\', \'%%id%%\', null, this ); return false;"', 'title' => ts('File on Case'), - ), - ); + ], + ]; } if ($showDelete) { if (!isset($delUrl) || !$delUrl) { $delUrl = $url; } - $actionLinks += array( + $actionLinks += [ CRM_Core_Action:: - DELETE => array( + DELETE => [ 'name' => ts('Delete'), 'url' => $delUrl, 'qs' => $qsDelete, 'title' => ts('Delete Activity'), - ), - ); + ], + ]; } if ($accessMailingReport) { - $actionLinks += array( + $actionLinks += [ CRM_Core_Action:: - BROWSE => array( + BROWSE => [ 'name' => ts('Mailing Report'), 'url' => 'civicrm/mailing/report', 'qs' => "mid={$sourceRecordId}&reset=1&cid=%%cid%%&context=activitySelector", 'title' => ts('View Mailing Report'), - ), - ); + ], + ]; } return $actionLinks; @@ -317,7 +321,7 @@ public function getPagerParams($action, &$params) { */ public function &getColumnHeaders($action = NULL, $output = NULL) { if ($output == CRM_Core_Selector_Controller::EXPORT || $output == CRM_Core_Selector_Controller::SCREEN) { - $csvHeaders = array(ts('Activity Type'), ts('Description'), ts('Activity Date')); + $csvHeaders = [ts('Activity Type'), ts('Description'), ts('Activity Date')]; foreach (self::_getColumnHeaders() as $column) { if (array_key_exists('name', $column)) { $csvHeaders[] = $column['name']; @@ -342,7 +346,7 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { * Total number of rows */ public function getTotalCount($action, $case = NULL) { - $params = array( + $params = [ 'contact_id' => $this->_contactId, 'admin' => $this->_admin, 'caseId' => $case, @@ -351,8 +355,8 @@ public function getTotalCount($action, $case = NULL) { 'offset' => 0, 'rowCount' => 0, 'sort' => NULL, - ); - return CRM_Activity_BAO_Activity::deprecatedGetActivitiesCount($params); + ]; + return CRM_Activity_BAO_Activity::getActivitiesCount($params); } /** @@ -375,7 +379,7 @@ public function getTotalCount($action, $case = NULL) { * the total number of rows for this action */ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $case = NULL) { - $params = array( + $params = [ 'contact_id' => $this->_contactId, 'admin' => $this->_admin, 'caseId' => $case, @@ -384,9 +388,9 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $ca 'offset' => $offset, 'rowCount' => $rowCount, 'sort' => $sort, - ); + ]; $config = CRM_Core_Config::singleton(); - $rows = CRM_Activity_BAO_Activity::deprecatedGetActivities($params); + $rows = CRM_Activity_BAO_Activity::getActivities($params); if (empty($rows)) { return $rows; @@ -397,7 +401,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $ca $engagementLevels = CRM_Campaign_PseudoConstant::engagementLevel(); // CRM-4418 - $permissions = array($this->_permission); + $permissions = [$this->_permission]; if (CRM_Core_Permission::check('delete activities')) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -467,12 +471,12 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL, $ca if ($output != CRM_Core_Selector_Controller::EXPORT && $output != CRM_Core_Selector_Controller::SCREEN) { $row['action'] = CRM_Core_Action::formLink($actionLinks, $actionMask, - array( + [ 'id' => $row['activity_id'], 'cid' => $this->_contactId, 'cxt' => $this->_context, 'caseid' => CRM_Utils_Array::value('case_id', $row), - ), + ], ts('more'), FALSE, 'activity.selector.action', @@ -508,36 +512,36 @@ public function getExportFileName($output = 'csv') { */ private static function &_getColumnHeaders() { if (!isset(self::$_columnHeaders)) { - self::$_columnHeaders = array( - array( + self::$_columnHeaders = [ + [ 'name' => ts('Type'), 'sort' => 'activity_type', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Subject'), 'sort' => 'subject', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Added By'), 'sort' => 'source_contact_name', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array('name' => ts('With')), - array('name' => ts('Assigned')), - array( + ], + ['name' => ts('With')], + ['name' => ts('Assigned')], + [ 'name' => ts('Date'), 'sort' => 'activity_date_time', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Status'), 'sort' => 'status_id', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array('desc' => ts('Actions')), - ); + ], + ['desc' => ts('Actions')], + ]; } return self::$_columnHeaders; diff --git a/CRM/Activity/Selector/Search.php b/CRM/Activity/Selector/Search.php index 22ec458a59d2..77bb0fa9a832 100644 --- a/CRM/Activity/Selector/Search.php +++ b/CRM/Activity/Selector/Search.php @@ -1,9 +1,9 @@ $componentName) { // CRM-19201: Add support for searching CiviCampaign and CiviCase // activities. For CiviCase, "access all cases and activities" is // required here rather than "access my cases and activities" to // prevent those with only the later permission from seeing a list // of all cases which might present a privacy issue. + // @todo this is the cause of the current devastatingly bad performance on + // activity search - it involves a bad join. + // The correct fix is to use the permission infrastrucutre - ie. add in the + // clause generated by CRM_Activity_BAO_Query::addSelectWhere + // but some testing needs to check that before making the change + // see https://github.com/civicrm/civicrm-core/blob/be2fb01f90f5f299dd07402a41fed7c7c7567f00/CRM/Utils/SQL.php#L48 + // for how it's done in the api kernel. if (!CRM_Core_Permission::access($componentName, TRUE, TRUE)) { $componentClause[] = " (activity_type.component_id IS NULL OR activity_type.component_id <> {$componentID}) "; } @@ -197,9 +204,7 @@ public function __construct( // type of selector $this->_action = $action; $this->_query = new CRM_Contact_BAO_Query($this->_queryParams, - CRM_Activity_BAO_Query::defaultReturnProperties(CRM_Contact_BAO_Query::MODE_ACTIVITY, - FALSE - ), + CRM_Activity_BAO_Query::selectorReturnProperties(), NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_ACTIVITY ); @@ -263,7 +268,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { FALSE, $this->_activityClause ); - $rows = array(); + $rows = []; $mailingIDs = CRM_Mailing_BAO_Mailing::mailingACLIDs(); $accessCiviMail = CRM_Core_Permission::check('access CiviMail'); @@ -271,20 +276,20 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $allCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE); $engagementLevels = CRM_Campaign_PseudoConstant::engagementLevel(); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); - // Get all activity types - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name', TRUE); + $bulkActivityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Bulk Email'); while ($result->fetch()) { - $row = array(); + $row = []; // Ignore rows where we dont have an activity id. if (empty($result->activity_id)) { continue; } + $this->_query->convertToPseudoNames($result); // the columns we are interested in foreach (self::$_properties as $property) { @@ -312,9 +317,8 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $accessMailingReport = FALSE; $activityTypeId = $row['activity_type_id']; if ($row['activity_is_test']) { - $row['activity_type'] = $row['activity_type'] . " (test)"; + $row['activity_type'] = CRM_Core_TestEntity::appendTestText($row['activity_type']); } - $bulkActivityTypeID = CRM_Utils_Array::key('Bulk Email', $activityTypes); $row['mailingId'] = ''; if ( $accessCiviMail && @@ -338,11 +342,11 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $this->_compContext ); $row['action'] = CRM_Core_Action::formLink($actionLinks, NULL, - array( + [ 'id' => $result->activity_id, 'cid' => $contactId, 'cxt' => $this->_context, - ), + ], ts('more'), FALSE, 'activity.selector.row', @@ -364,7 +368,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $repeat = CRM_Core_BAO_RecurringEntity::getPositionAndCount($row['activity_id'], 'civicrm_activity'); $row['repeat'] = ''; if ($repeat) { - $row['repeat'] = ts('Repeating (%1 of %2)', array(1 => $repeat[0], 2 => $repeat[1])); + $row['repeat'] = ts('Repeating (%1 of %2)', [1 => $repeat[0], 2 => $repeat[1]]); } $rows[] = $row; } @@ -394,38 +398,38 @@ public function getQILL() { */ public function &getColumnHeaders($action = NULL, $output = NULL) { if (!isset(self::$_columnHeaders)) { - self::$_columnHeaders = array( - array( + self::$_columnHeaders = [ + [ 'name' => ts('Type'), 'sort' => 'activity_type_id', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Subject'), 'sort' => 'activity_subject', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Added By'), 'sort' => 'source_contact', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array('name' => ts('With')), - array('name' => ts('Assigned')), - array( + ], + ['name' => ts('With')], + ['name' => ts('Assigned')], + [ 'name' => ts('Date'), 'sort' => 'activity_date_time', 'direction' => CRM_Utils_Sort::DESCENDING, - ), - array( + ], + [ 'name' => ts('Status'), 'sort' => 'activity_status', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'desc' => ts('Actions'), - ), - ); + ], + ]; } return self::$_columnHeaders; } @@ -434,11 +438,11 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { * @return mixed */ public function alphabetQuery() { - return $this->_query->searchQuery(NULL, NULL, NULL, FALSE, FALSE, TRUE); + return $this->_query->alphabetQuery(); } /** - * @return string + * @return \CRM_Contact_BAO_Query */ public function &getQuery() { return $this->_query; diff --git a/CRM/Activity/StateMachine/Search.php b/CRM/Activity/StateMachine/Search.php index 6aa81146fe0d..237e9704fdf3 100644 --- a/CRM/Activity/StateMachine/Search.php +++ b/CRM/Activity/StateMachine/Search.php @@ -1,9 +1,9 @@ _pages = array(); + $this->_pages = []; $this->_pages['CRM_Activity_Form_Search'] = NULL; list($task, $result) = $this->taskName($controller, 'Search'); @@ -76,7 +76,7 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { * * @param string $formName * - * @return string + * @return array * the name of the form that will handle the task */ public function taskName($controller, $formName = 'Search') { diff --git a/CRM/Activity/Task.php b/CRM/Activity/Task.php index dbb2e6307d3f..00bfcbffd952 100644 --- a/CRM/Activity/Task.php +++ b/CRM/Activity/Task.php @@ -1,9 +1,9 @@ array( + self::$_tasks = [ + self::TASK_DELETE => [ 'title' => ts('Delete activities'), 'class' => 'CRM_Activity_Form_Task_Delete', 'result' => FALSE, - ), - 2 => array( + ], + self::TASK_PRINT => [ 'title' => ts('Print selected rows'), 'class' => 'CRM_Activity_Form_Task_Print', 'result' => FALSE, - ), - 3 => array( + ], + self::TASK_EXPORT => [ 'title' => ts('Export activities'), - 'class' => array( + 'class' => [ 'CRM_Export_Form_Select', 'CRM_Export_Form_Map', - ), + ], 'result' => FALSE, - ), - 4 => array( + ], + self::BATCH_UPDATE => [ 'title' => ts('Update multiple activities'), - 'class' => array( + 'class' => [ 'CRM_Activity_Form_Task_PickProfile', 'CRM_Activity_Form_Task_Batch', - ), + ], 'result' => FALSE, - ), - 5 => array( - 'title' => ts('Email - send now'), - 'class' => array( + ], + self::TASK_EMAIL => [ + 'title' => ts('Email - send now (to %1 or less)', [ + 1 => Civi::settings() + ->get('simple_mail_limit'), + ]), + 'class' => [ 'CRM_Activity_Form_Task_PickOption', 'CRM_Activity_Form_Task_Email', - ), + ], 'result' => FALSE, - ), - 6 => array( + ], + self::TASK_SMS => [ 'title' => ts('SMS - send reply'), 'class' => 'CRM_Activity_Form_Task_SMS', 'result' => FALSE, - ), - 7 => array( + ], + self::TAG_ADD => [ 'title' => ts('Tag - add to activities'), 'class' => 'CRM_Activity_Form_Task_AddToTag', 'result' => FALSE, - ), - 8 => array( + ], + self::TAG_REMOVE => [ 'title' => ts('Tag - remove from activities'), 'class' => 'CRM_Activity_Form_Task_RemoveFromTag', 'result' => FALSE, - ), - ); + ], + ]; $config = CRM_Core_Config::singleton(); if (in_array('CiviCase', $config->enableComponents)) { if (CRM_Core_Permission::check('access all cases and activities') || CRM_Core_Permission::check('access my cases and activities') ) { - self::$_tasks[6] = array( + self::$_tasks[self::TASK_SMS] = [ 'title' => ts('File on case'), 'class' => 'CRM_Activity_Form_Task_FileOnCase', 'result' => FALSE, - ); + ]; } } // CRM-4418, check for delete if (!CRM_Core_Permission::check('delete activities')) { - unset(self::$_tasks[1]); + unset(self::$_tasks[self::TASK_DELETE]); } - CRM_Utils_Hook::searchTasks('activity', self::$_tasks); - asort(self::$_tasks); + parent::tasks(); } return self::$_tasks; } - /** - * These tasks are the core set of task titles on activity. - * - * @return array - * the set of task titles - */ - public static function &taskTitles() { - self::tasks(); - $titles = array(); - foreach (self::$_tasks as $id => $value) { - $titles[$id] = $value['title']; - } - return $titles; - } - /** * Show tasks selectively based on the permission level of the user. * * @param int $permission + * @param array $params * * @return array * set of tasks that are valid for the user */ - public static function &permissionedTaskTitles($permission) { - $tasks = array(); + public static function permissionedTaskTitles($permission, $params = []) { if ($permission == CRM_Core_Permission::EDIT) { $tasks = self::taskTitles(); } else { - $tasks = array( - 3 => self::$_tasks[3]['title'], - ); + $tasks = [ + self::TASK_EXPORT => self::$_tasks[self::TASK_EXPORT]['title'], + ]; //CRM-4418, if (CRM_Core_Permission::check('delete activities')) { - $tasks[1] = self::$_tasks[1]['title']; + $tasks[self::TASK_DELETE] = self::$_tasks[self::TASK_DELETE]['title']; } } + + $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params); return $tasks; } @@ -195,12 +165,13 @@ public static function getTask($value) { self::tasks(); if (!$value || !CRM_Utils_Array::value($value, self::$_tasks)) { // make the print task by default - $value = 2; + $value = self::TASK_PRINT; } - return array( + + return [ self::$_tasks[$value]['class'], self::$_tasks[$value]['result'], - ); + ]; } } diff --git a/CRM/Activity/Tokens.php b/CRM/Activity/Tokens.php index 7a0ba27d4096..3271b965922f 100644 --- a/CRM/Activity/Tokens.php +++ b/CRM/Activity/Tokens.php @@ -2,9 +2,9 @@ /* +--------------------------------------------------------------------+ - | CiviCRM version 4.7 | + | CiviCRM version 5 | +--------------------------------------------------------------------+ - | Copyright CiviCRM LLC (c) 2004-2017 | + | Copyright CiviCRM LLC (c) 2004-2019 | +--------------------------------------------------------------------+ | This file is a part of CiviCRM. | | | @@ -28,7 +28,7 @@ /** * @package CRM - * @copyright CiviCRM LLC (c) 2004-2017 + * @copyright CiviCRM LLC (c) 2004-2019 */ /** @@ -50,14 +50,8 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber { */ public function __construct() { parent::__construct('activity', array_merge( - array( - 'activity_id' => ts('Activity ID'), - 'activity_type' => ts('Activity Type'), - 'subject' => ts('Activity Subject'), - 'details' => ts('Activity Details'), - 'activity_date_time' => ts('Activity Date-Time'), - ), - $this->getCustomTokens('Activity') + $this->getBasicTokens(), + $this->getCustomFieldTokens() )); } @@ -66,8 +60,7 @@ public function __construct() { */ public function checkActive(\Civi\Token\TokenProcessor $processor) { // Extracted from scheduled-reminders code. See the class description. - return - !empty($processor->context['actionMapping']) + return !empty($processor->context['actionMapping']) && $processor->context['actionMapping']->getEntity() === 'civicrm_activity'; } @@ -84,7 +77,8 @@ public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQuery // Q: Could we simplify & move the extra AND clauses into `where(...)`? $e->query->param('casEntityJoinExpr', 'e.id = reminder.entity_id AND e.is_current_revision = 1 AND e.is_deleted = 0'); - $e->query->select('e.*'); // FIXME: seems too broad. + // FIXME: seems too broad. + $e->query->select('e.*'); $e->query->select('ov.label as activity_type, e.id as activity_id'); $e->query->join("og", "!casMailingJoinType civicrm_option_group og ON og.name = 'activity_type'"); @@ -118,4 +112,27 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe } } + /** + * Get the basic tokens provided. + * + * @return array token name => token label + */ + protected function getBasicTokens() { + return [ + 'activity_id' => ts('Activity ID'), + 'activity_type' => ts('Activity Type'), + 'subject' => ts('Activity Subject'), + 'details' => ts('Activity Details'), + 'activity_date_time' => ts('Activity Date-Time'), + ]; + } + + /** + * Get the tokens for custom fields + * @return array token name => token label + */ + protected function getCustomFieldTokens() { + return CRM_Utils_Token::getCustomFieldTokens('Activity'); + } + } diff --git a/CRM/Admin/Form.php b/CRM/Admin/Form.php index 8129e3f18466..e4edfb0a7c60 100644 --- a/CRM/Admin/Form.php +++ b/CRM/Admin/Form.php @@ -1,9 +1,9 @@ addStyleFile('civicrm', 'css/admin.css'); - Civi::resources()->addScriptFile('civicrm', 'js/crm.admin.js'); + Civi::resources()->addScriptFile('civicrm', 'js/jquery/jquery.crmIconPicker.js'); $this->_id = $this->get('id'); $this->_BAOName = $this->get('BAOName'); - $this->_values = array(); + $this->_values = []; if (isset($this->_id)) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; // this is needed if the form is outside the CRM name space $baoName = $this->_BAOName; $baoName::retrieve($params, $this->_values); @@ -92,8 +92,8 @@ public function preProcess() { public function setDefaultValues() { // Fetch defaults from the db if (!empty($this->_id) && empty($this->_values) && CRM_Utils_Rule::positiveInteger($this->_id)) { - $this->_values = array(); - $params = array('id' => $this->_id); + $this->_values = []; + $params = ['id' => $this->_id]; $baoName = $this->_BAOName; $baoName::retrieve($params, $this->_values); } @@ -127,28 +127,26 @@ public function setDefaultValues() { */ public function buildQuickForm() { if ($this->_action & CRM_Core_Action::VIEW || $this->_action & CRM_Core_Action::PREVIEW) { - $this->addButtons(array( - array( - 'type' => 'cancel', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'cancel', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } else { - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => $this->_action & CRM_Core_Action::DELETE ? ts('Delete') : ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => $this->_action & CRM_Core_Action::DELETE ? ts('Delete') : ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } } diff --git a/CRM/Admin/Form/CMSUser.php b/CRM/Admin/Form/CMSUser.php index ee79cc774216..44305056b654 100644 --- a/CRM/Admin/Form/CMSUser.php +++ b/CRM/Admin/Form/CMSUser.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'next', - 'name' => ts('OK'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('OK'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -62,25 +61,25 @@ public function postProcess() { $result = CRM_Utils_System::synchronizeUsers(); $status = ts('Checked one user record.', - array( + [ 'count' => $result['contactCount'], 'plural' => 'Checked %count user records.', - ) + ] ); if ($result['contactMatching']) { $status .= '
' . ts('Found one matching contact record.', - array( + [ 'count' => $result['contactMatching'], 'plural' => 'Found %count matching contact records.', - ) + ] ); } $status .= '
' . ts('Created one new contact record.', - array( + [ 'count' => $result['contactCreated'], 'plural' => 'Created %count new contact records.', - ) + ] ); CRM_Core_Session::setStatus($status, ts('Synchronize Complete'), 'success'); CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); diff --git a/CRM/Admin/Form/ContactType.php b/CRM/Admin/Form/ContactType.php index 9e50d1427358..3e6f2a753646 100644 --- a/CRM/Admin/Form/ContactType.php +++ b/CRM/Admin/Form/ContactType.php @@ -1,9 +1,9 @@ assign('cid', $this->_id); - $this->addFormRule(array('CRM_Admin_Form_ContactType', 'formRule'), $this); + $this->addFormRule(['CRM_Admin_Form_ContactType', 'formRule'], $this); } /** @@ -90,7 +90,7 @@ public function buildQuickForm() { */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; if ($self->_id) { $contactName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_ContactType', $self->_id, 'name'); @@ -142,7 +142,7 @@ public function postProcess() { } $contactType = CRM_Contact_BAO_ContactType::add($params); CRM_Core_Session::setStatus(ts("The Contact Type '%1' has been saved.", - array(1 => $contactType->label) + [1 => $contactType->label] ), ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/Extensions.php b/CRM/Admin/Form/Extensions.php index 2373bcb40d46..6826a159d4d8 100644 --- a/CRM/Admin/Form/Extensions.php +++ b/CRM/Admin/Form/Extensions.php @@ -1,9 +1,9 @@ formatLocalExtensionRows(); + $this->assign('localExtensionRows', $localExtensionRows); + + $remoteExtensionRows = $mainPage->formatRemoteExtensionRows($localExtensionRows); + $this->assign('remoteExtensionRows', $remoteExtensionRows); + $this->_key = CRM_Utils_Request::retrieve('key', 'String', $this, FALSE, 0 ); - + if (!CRM_Utils_Type::validate($this->_key, 'ExtensionKey') && !empty($this->_key)) { + throw new CRM_Core_Exception('Extension Key does not match expected standard'); + } $session = CRM_Core_Session::singleton(); $url = CRM_Utils_System::url('civicrm/admin/extensions', 'reset=1&action=browse'); $session->pushUserContext($url); @@ -81,7 +90,7 @@ public function preProcess() { * Set default values for the form. */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; return $defaults; } @@ -92,53 +101,52 @@ public function buildQuickForm() { switch ($this->_action) { case CRM_Core_Action::ADD: $buttonName = ts('Install'); - $title = ts('Install "%1"?', array( + $title = ts('Install "%1"?', [ 1 => $this->_key, - )); + ]); break; case CRM_Core_Action::UPDATE: $buttonName = ts('Download and Install'); - $title = ts('Download and Install "%1"?', array( + $title = ts('Download and Install "%1"?', [ 1 => $this->_key, - )); + ]); break; case CRM_Core_Action::DELETE: $buttonName = ts('Uninstall'); - $title = ts('Uninstall "%1"?', array( + $title = ts('Uninstall "%1"?', [ 1 => $this->_key, - )); + ]); break; case CRM_Core_Action::ENABLE: $buttonName = ts('Enable'); - $title = ts('Enable "%1"?', array( + $title = ts('Enable "%1"?', [ 1 => $this->_key, - )); + ]); break; case CRM_Core_Action::DISABLE: $buttonName = ts('Disable'); - $title = ts('Disable "%1"?', array( + $title = ts('Disable "%1"?', [ 1 => $this->_key, - )); + ]); break; } $this->assign('title', $title); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => $buttonName, - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => $buttonName, + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -155,7 +163,7 @@ public function buildQuickForm() { * true if no errors, else an array of errors */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; return empty($errors) ? TRUE : $errors; } @@ -168,7 +176,7 @@ public function postProcess() { if ($this->_action & CRM_Core_Action::DELETE) { try { - CRM_Extension_System::singleton()->getManager()->uninstall(array($this->_key)); + CRM_Extension_System::singleton()->getManager()->uninstall([$this->_key]); CRM_Core_Session::setStatus("", ts('Extension Uninstalled'), "success"); } catch (CRM_Extension_Exception_DependencyException $e) { @@ -178,26 +186,26 @@ public function postProcess() { } if ($this->_action & CRM_Core_Action::ADD) { - CRM_Extension_System::singleton()->getManager()->install(array($this->_key)); + civicrm_api3('Extension', 'install', ['keys' => $this->_key]); CRM_Core_Session::setStatus("", ts('Extension Installed'), "success"); } if ($this->_action & CRM_Core_Action::ENABLE) { - CRM_Extension_System::singleton()->getManager()->enable(array($this->_key)); + civicrm_api3('Extension', 'enable', ['keys' => $this->_key]); CRM_Core_Session::setStatus("", ts('Extension Enabled'), "success"); } if ($this->_action & CRM_Core_Action::DISABLE) { - CRM_Extension_System::singleton()->getManager()->disable(array($this->_key)); + CRM_Extension_System::singleton()->getManager()->disable([$this->_key]); CRM_Core_Session::setStatus("", ts('Extension Disabled'), "success"); } if ($this->_action & CRM_Core_Action::UPDATE) { - $result = civicrm_api('Extension', 'download', array( + $result = civicrm_api('Extension', 'download', [ 'version' => 3, 'key' => $this->_key, - )); - if (!CRM_Utils_Array::value('is_error', $result, FALSE)) { + ]); + if (empty($result['is_error'])) { CRM_Core_Session::setStatus("", ts('Extension Upgraded'), "success"); } else { diff --git a/CRM/Admin/Form/Generic.php b/CRM/Admin/Form/Generic.php new file mode 100644 index 000000000000..04b29ecd9793 --- /dev/null +++ b/CRM/Admin/Form/Generic.php @@ -0,0 +1,112 @@ +setDefaultsForMetadataDefinedFields(); + return $this->_defaults; + } + + /** + * Build the form object. + */ + public function buildQuickForm() { + $filter = $this->getSettingPageFilter(); + $settings = civicrm_api3('Setting', 'getfields', [])['values']; + foreach ($settings as $key => $setting) { + if (isset($setting['settings_pages'][$filter])) { + $this->_settings[$key] = $setting; + } + } + + $this->addFieldsDefinedInSettingsMetadata(); + + // @todo look at sharing the code below in the settings trait. + if ($this->includesReadOnlyFields) { + CRM_Core_Session::setStatus(ts("Some fields are loaded as 'readonly' as they have been set (overridden) in civicrm.settings.php."), '', 'info', ['expires' => 0]); + } + + // @todo - do we still like this redirect? + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + } + + /** + * Process the form submission. + */ + public function postProcess() { + $params = $this->controller->exportValues($this->_name); + try { + $this->saveMetadataDefinedSettings($params); + } + catch (CiviCRM_API3_Exception $e) { + CRM_Core_Session::setStatus($e->getMessage(), ts('Save Failed'), 'error'); + } + } + +} diff --git a/CRM/Admin/Form/Job.php b/CRM/Admin/Form/Job.php index 9003bda4e583..42f4a3c3861d 100644 --- a/CRM/Admin/Form/Job.php +++ b/CRM/Admin/Form/Job.php @@ -1,9 +1,9 @@ addRule('name', ts('Name already exists in Database.'), 'objectExists', array( - 'CRM_Core_DAO_Job', - $this->_id, - )); + $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', [ + 'CRM_Core_DAO_Job', + $this->_id, + ]); $this->add('text', 'description', ts('Description'), $attributes['description'] @@ -97,7 +97,7 @@ public function buildQuickForm($check = FALSE) { $this->add('select', 'run_frequency', ts('Run frequency'), CRM_Core_SelectValues::getJobFrequency()); // CRM-17686 - $this->add('datepicker', 'scheduled_run_date', ts('Scheduled Run Date'), NULL, FALSE, array('minDate' => time())); + $this->add('datepicker', 'scheduled_run_date', ts('Scheduled Run Date'), NULL, FALSE, ['minDate' => time()]); $this->add('textarea', 'parameters', ts('Command parameters'), "cols=50 rows=6" @@ -106,7 +106,7 @@ public function buildQuickForm($check = FALSE) { // is this job active ? $this->add('checkbox', 'is_active', ts('Is this Scheduled Job active?')); - $this->addFormRule(array('CRM_Admin_Form_Job', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Job', 'formRule']); } /** @@ -117,13 +117,13 @@ public function buildQuickForm($check = FALSE) { */ public static function formRule($fields) { - $errors = array(); + $errors = []; require_once 'api/api.php'; /** @var \Civi\API\Kernel $apiKernel */ $apiKernel = \Civi::service('civi_api_kernel'); - $apiRequest = \Civi\API\Request::create($fields['api_entity'], $fields['api_action'], array('version' => 3), NULL); + $apiRequest = \Civi\API\Request::create($fields['api_entity'], $fields['api_action'], ['version' => 3], NULL); try { $apiKernel->resolve($apiRequest); } @@ -142,7 +142,7 @@ public static function formRule($fields) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!$this->_id) { $defaults['is_active'] = $defaults['is_default'] = 1; @@ -180,7 +180,7 @@ public function setDefaultValues() { */ public function postProcess() { - CRM_Utils_System::flushCache('CRM_Core_DAO_Job'); + CRM_Utils_System::flushCache(); if ($this->_action & CRM_Core_Action::DELETE) { CRM_Core_BAO_Job::del($this->_id); @@ -217,13 +217,13 @@ public function postProcess() { The result will land on the same day of the month except for days 29-31 when the target month contains fewer days than the previous month. For example, if a job is scheduled to run on August 31st, the following invocation will occur on October 1st, and then the 1st of every month thereafter. To avoid this issue, please schedule Monthly and Quarterly jobs to run within the first 28 days of the month.'), - ts('Warning'), 'info', array('expires' => 0)); + ts('Warning'), 'info', ['expires' => 0]); } } } // ...otherwise, if this isn't a new scheduled job, clear the next scheduled run elseif ($dao->id) { - $job = new CRM_Core_ScheduledJob(array('id' => $dao->id)); + $job = new CRM_Core_ScheduledJob(['id' => $dao->id]); $job->clearScheduledRunDate(); } @@ -233,7 +233,7 @@ public function postProcess() { if ($values['api_action'] == 'update_greeting' && CRM_Utils_Array::value('is_active', $values) == 1) { // pass "wiki" as 6th param to docURL2 if you are linking to a page in wiki.civicrm.org $docLink = CRM_Utils_System::docURL2("Managing Scheduled Jobs", NULL, NULL, NULL, NULL, "wiki"); - $msg = ts('The update greeting job can be very resource intensive and is typically not necessary to run on a regular basis. If you do choose to enable the job, we recommend you do not run it with the force=1 option, which would rebuild greetings on all records. Leaving that option absent, or setting it to force=0, will only rebuild greetings for contacts that do not currently have a value stored. %1', array(1 => $docLink)); + $msg = ts('The update greeting job can be very resource intensive and is typically not necessary to run on a regular basis. If you do choose to enable the job, we recommend you do not run it with the force=1 option, which would rebuild greetings on all records. Leaving that option absent, or setting it to force=0, will only rebuild greetings for contacts that do not currently have a value stored. %1', [1 => $docLink]); CRM_Core_Session::setStatus($msg, ts('Warning: Update Greeting job enabled'), 'alert'); } diff --git a/CRM/Admin/Form/LabelFormats.php b/CRM/Admin/Form/LabelFormats.php index 73ef984e89cd..6e3ad025df0c 100644 --- a/CRM/Admin/Form/LabelFormats.php +++ b/CRM/Admin/Form/LabelFormats.php @@ -1,7 +1,7 @@ _id = $this->get('id'); $this->_group = CRM_Utils_Request::retrieve('group', 'String', $this, FALSE, 'label_format'); - $this->_values = array(); + $this->_values = []; if (isset($this->_id)) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; CRM_Core_BAO_LabelFormat::retrieve($params, $this->_values, $this->_group); } } @@ -69,7 +71,7 @@ public function buildQuickForm() { return; } - $disabled = array(); + $disabled = []; $required = TRUE; $is_reserved = $this->_id ? CRM_Core_BAO_LabelFormat::getFieldValue('CRM_Core_BAO_LabelFormat', $this->_id, 'is_reserved') : FALSE; if ($is_reserved) { @@ -79,7 +81,7 @@ public function buildQuickForm() { $attributes = CRM_Core_DAO::getAttribute('CRM_Core_BAO_LabelFormat'); $this->add('text', 'label', ts('Name'), $attributes['label'] + $disabled, $required); - $this->add('text', 'description', ts('Description'), array('size' => CRM_Utils_Type::HUGE)); + $this->add('text', 'description', ts('Description'), ['size' => CRM_Utils_Type::HUGE]); $this->add('checkbox', 'is_default', ts('Is this Label Format the default?')); // currently we support only mailing label creation, hence comment below code @@ -97,18 +99,18 @@ public function buildQuickForm() { */ $this->add('select', 'paper_size', ts('Sheet Size'), - array( + [ 0 => ts('- default -'), - ) + CRM_Core_BAO_PaperSize::getList(TRUE), FALSE, - array( + ] + CRM_Core_BAO_PaperSize::getList(TRUE), FALSE, + [ 'onChange' => "selectPaper( this.value );", - ) + $disabled + ] + $disabled ); $this->add('static', 'paper_dimensions', NULL, ts('Sheet Size (w x h)')); $this->add('select', 'orientation', ts('Orientation'), CRM_Core_BAO_LabelFormat::getPageOrientations(), FALSE, - array( + [ 'onChange' => "updatePaperDimensions();", - ) + $disabled + ] + $disabled ); $this->add('select', 'font_name', ts('Font Name'), CRM_Core_BAO_LabelFormat::getFontNames($this->_group)); $this->add('select', 'font_size', ts('Font Size'), CRM_Core_BAO_LabelFormat::getFontSizes()); @@ -116,24 +118,24 @@ public function buildQuickForm() { $this->add('checkbox', 'bold', ts('Bold')); $this->add('checkbox', 'italic', ts('Italic')); $this->add('select', 'metric', ts('Unit of Measure'), CRM_Core_BAO_LabelFormat::getUnits(), FALSE, - array('onChange' => "selectMetric( this.value );") + ['onChange' => "selectMetric( this.value );"] ); - $this->add('text', 'width', ts('Label Width'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'height', ts('Label Height'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'NX', ts('Labels Per Row'), array('size' => 3, 'maxlength' => 3) + $disabled, $required); - $this->add('text', 'NY', ts('Labels Per Column'), array('size' => 3, 'maxlength' => 3) + $disabled, $required); - $this->add('text', 'tMargin', ts('Top Margin'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'lMargin', ts('Left Margin'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'SpaceX', ts('Horizontal Spacing'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'SpaceY', ts('Vertical Spacing'), array('size' => 8, 'maxlength' => 8) + $disabled, $required); - $this->add('text', 'lPadding', ts('Left Padding'), array('size' => 8, 'maxlength' => 8), $required); - $this->add('text', 'tPadding', ts('Top Padding'), array('size' => 8, 'maxlength' => 8), $required); - $this->add('text', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_BAO_LabelFormat', 'weight'), TRUE); - - $this->addRule('label', ts('Name already exists in Database.'), 'objectExists', array( + $this->add('text', 'width', ts('Label Width'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'height', ts('Label Height'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'NX', ts('Labels Per Row'), ['size' => 3, 'maxlength' => 3] + $disabled, $required); + $this->add('text', 'NY', ts('Labels Per Column'), ['size' => 3, 'maxlength' => 3] + $disabled, $required); + $this->add('text', 'tMargin', ts('Top Margin'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'lMargin', ts('Left Margin'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'SpaceX', ts('Horizontal Spacing'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'SpaceY', ts('Vertical Spacing'), ['size' => 8, 'maxlength' => 8] + $disabled, $required); + $this->add('text', 'lPadding', ts('Left Padding'), ['size' => 8, 'maxlength' => 8], $required); + $this->add('text', 'tPadding', ts('Top Padding'), ['size' => 8, 'maxlength' => 8], $required); + $this->add('number', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_BAO_LabelFormat', 'weight'), TRUE); + + $this->addRule('label', ts('Name already exists in Database.'), 'objectExists', [ 'CRM_Core_BAO_LabelFormat', $this->_id, - )); + ]); $this->addRule('NX', ts('Please enter a valid integer.'), 'integer'); $this->addRule('NY', ts('Please enter a valid integer.'), 'integer'); $this->addRule('tMargin', ts('Please enter a valid number.'), 'numeric'); @@ -186,14 +188,14 @@ public function postProcess() { if ($this->_action & CRM_Core_Action::COPY) { // make a copy of the Label Format $labelFormat = CRM_Core_BAO_LabelFormat::getById($this->_id, $this->_group); - $newlabel = ts('Copy of %1', array(1 => $labelFormat['label'])); + $newlabel = ts('Copy of %1', [1 => $labelFormat['label']]); $list = CRM_Core_BAO_LabelFormat::getList(TRUE, $this->_group); $count = 1; while (in_array($newlabel, $list)) { $count++; - $newlabel = ts('Copy %1 of %2', array(1 => $count, 2 => $labelFormat['label'])); + $newlabel = ts('Copy %1 of %2', [1 => $count, 2 => $labelFormat['label']]); } $labelFormat['label'] = $newlabel; @@ -203,7 +205,7 @@ public function postProcess() { $bao = new CRM_Core_BAO_LabelFormat(); $bao->saveLabelFormat($labelFormat, NULL, $this->_group); - CRM_Core_Session::setStatus(ts('%1 has been created.', array(1 => $labelFormat['label'])), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts('%1 has been created.', [1 => $labelFormat['label']]), ts('Saved'), 'success'); return; } @@ -240,9 +242,9 @@ public function postProcess() { $bao = new CRM_Core_BAO_LabelFormat(); $bao->saveLabelFormat($values, $this->_id, $values['label_type']); - $status = ts('Your new Label Format titled %1 has been saved.', array(1 => $values['label'])); + $status = ts('Your new Label Format titled %1 has been saved.', [1 => $values['label']]); if ($this->_action & CRM_Core_Action::UPDATE) { - $status = ts('Your Label Format titled %1 has been updated.', array(1 => $values['label'])); + $status = ts('Your Label Format titled %1 has been updated.', [1 => $values['label']]); } CRM_Core_Session::setStatus($status, ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/LocationType.php b/CRM/Admin/Form/LocationType.php index 9c6dbb723b55..65e43aff0b12 100644 --- a/CRM/Admin/Form/LocationType.php +++ b/CRM/Admin/Form/LocationType.php @@ -1,9 +1,9 @@ addRule('name', ts('Name already exists in Database.'), 'objectExists', - array('CRM_Core_DAO_LocationType', $this->_id) + ['CRM_Core_DAO_LocationType', $this->_id] ); $this->addRule('name', ts('Name can only consist of alpha-numeric characters'), @@ -74,10 +74,10 @@ public function buildQuickForm() { if ($this->_action & CRM_Core_Action::UPDATE) { if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_LocationType', $this->_id, 'is_reserved')) { - $this->freeze(array('name', 'description', 'is_active')); + $this->freeze(['name', 'description', 'is_active']); } if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_LocationType', $this->_id, 'is_default')) { - $this->freeze(array('is_default')); + $this->freeze(['is_default']); } } } @@ -86,7 +86,7 @@ public function buildQuickForm() { * Process the form submission. */ public function postProcess() { - CRM_Utils_System::flushCache('CRM_Core_DAO_LocationType'); + CRM_Utils_System::flushCache(); if ($this->_action & CRM_Core_Action::DELETE) { CRM_Core_BAO_LocationType::del($this->_id); @@ -120,7 +120,7 @@ public function postProcess() { $locationType->save(); CRM_Core_Session::setStatus(ts("The location type '%1' has been saved.", - array(1 => $locationType->name) + [1 => $locationType->name] ), ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/MailSettings.php b/CRM/Admin/Form/MailSettings.php index a40b3f2adcd2..b87ccd961047 100644 --- a/CRM/Admin/Form/MailSettings.php +++ b/CRM/Admin/Form/MailSettings.php @@ -1,9 +1,9 @@ add('select', 'protocol', ts('Protocol'), - array('' => ts('- select -')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol'), + ['' => ts('- select -')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol'), TRUE ); $this->add('text', 'server', ts('Server'), $attributes['server']); - $this->add('text', 'username', ts('Username'), array('autocomplete' => 'off')); + $this->add('text', 'username', ts('Username'), ['autocomplete' => 'off']); - $this->add('password', 'password', ts('Password'), array('autocomplete' => 'off')); + $this->add('password', 'password', ts('Password'), ['autocomplete' => 'off']); $this->add('text', 'source', ts('Source'), $attributes['source']); $this->add('checkbox', 'is_ssl', ts('Use SSL?')); - $usedfor = array( + $usedfor = [ 1 => ts('Bounce Processing'), 0 => ts('Email-to-Activity Processing'), - ); + ]; $this->add('select', 'is_default', ts('Used For?'), $usedfor); - $this->addField('activity_status', array('placeholder' => FALSE)); + $this->addField('activity_status', ['placeholder' => FALSE]); } /** * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Admin_Form_MailSettings', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_MailSettings', 'formRule']); } public function getDefaultEntity() { @@ -125,7 +125,7 @@ public function setDefaultValues() { * list of errors to be posted back to the form */ public static function formRule($fields) { - $errors = array(); + $errors = []; // Check for default from email address and organization (domain) name. Force them to change it. if ($fields['domain'] == 'EXAMPLE.ORG') { $errors['domain'] = ts('Please enter a valid domain for this mailbox account (the part after @).'); @@ -148,7 +148,7 @@ public function postProcess() { $formValues = $this->controller->exportValues($this->_name); //form fields. - $fields = array( + $fields = [ 'name', 'domain', 'localpart', @@ -162,14 +162,14 @@ public function postProcess() { 'is_ssl', 'is_default', 'activity_status', - ); + ]; - $params = array(); + $params = []; foreach ($fields as $f) { - if (in_array($f, array( + if (in_array($f, [ 'is_default', 'is_ssl', - ))) { + ])) { $params[$f] = CRM_Utils_Array::value($f, $formValues, FALSE); } else { diff --git a/CRM/Admin/Form/Mapping.php b/CRM/Admin/Form/Mapping.php index 8286b9c6495f..2dac50e76328 100644 --- a/CRM/Admin/Form/Mapping.php +++ b/CRM/Admin/Form/Mapping.php @@ -1,9 +1,9 @@ add('text', 'name', ts('Name'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Mapping', 'name'), TRUE ); - $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', array( - 'CRM_Core_DAO_Mapping', - $this->_id, - )); + $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', [ + 'CRM_Core_DAO_Mapping', + $this->_id, + ]); $this->addElement('text', 'description', ts('Description'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Mapping', 'description') diff --git a/CRM/Admin/Form/MessageTemplates.php b/CRM/Admin/Form/MessageTemplates.php index c7ef91f53002..06f0259ec921 100644 --- a/CRM/Admin/Form/MessageTemplates.php +++ b/CRM/Admin/Form/MessageTemplates.php @@ -1,9 +1,9 @@ _workflow_id = CRM_Utils_Array::value('workflow_id', $defaults); - $this->assign('workflow_id', $this->_workflow_id); if ($this->_action & CRM_Core_Action::ADD) { $defaults['is_active'] = 1; - //set the context for redirection after form submit or cancel - $session = CRM_Core_Session::singleton(); - $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/messageTemplates', - 'selectedChild=user&reset=1' - )); } - // FIXME: we need to fix the Cancel button here as we don’t know whether it’s a workflow template in buildQuickForm() if ($this->_action & CRM_Core_Action::UPDATE) { - if ($this->_workflow_id) { - $selectedChild = 'workflow'; - } - else { - $selectedChild = 'user'; - } - $documentInfo = CRM_Core_BAO_File::getEntityFile('civicrm_msg_template', $this->_id, TRUE); if (!empty($documentInfo)) { $defaults['file_type'] = 1; $this->_is_document = TRUE; $this->assign('attachment', $documentInfo); } - - $cancelURL = CRM_Utils_System::url('civicrm/admin/messageTemplates', "selectedChild={$selectedChild}&reset=1"); - $cancelURL = str_replace('&', '&', $cancelURL); - $this->addButtons( - array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - 'js' => array('onclick' => "location.href='{$cancelURL}'; return false;"), - ), - ) - ); } return $defaults; @@ -121,55 +95,71 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - // For VIEW we only want Done button if ($this->_action & CRM_Core_Action::VIEW) { // currently, the above action is used solely for previewing default workflow templates $cancelURL = CRM_Utils_System::url('civicrm/admin/messageTemplates', 'selectedChild=workflow&reset=1'); $cancelURL = str_replace('&', '&', $cancelURL); - $this->addButtons(array( - array( - 'type' => 'cancel', - 'name' => ts('Done'), - 'js' => array('onclick' => "location.href='{$cancelURL}'; return false;"), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'cancel', + 'name' => ts('Done'), + 'js' => ['onclick' => "location.href='{$cancelURL}'; return false;"], + 'isDefault' => TRUE, + ], + ]); } else { - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => $this->_action & CRM_Core_Action::DELETE ? ts('Delete') : ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->_workflow_id = CRM_Utils_Array::value('workflow_id', $this->_values); + $this->checkUserPermission($this->_workflow_id); + $this->assign('workflow_id', $this->_workflow_id); + + if ($this->_workflow_id) { + $selectedChild = 'workflow'; + } + else { + $selectedChild = 'user'; + } + + $cancelURL = CRM_Utils_System::url('civicrm/admin/messageTemplates', "selectedChild={$selectedChild}&reset=1"); + $cancelURL = str_replace('&', '&', $cancelURL); + $buttons[] = [ + 'type' => 'upload', + 'name' => $this->_action & CRM_Core_Action::DELETE ? ts('Delete') : ts('Save'), + 'isDefault' => TRUE, + ]; + if (!($this->_action & CRM_Core_Action::DELETE)) { + $buttons[] = [ + 'type' => 'submit', + 'name' => ts('Save and Done'), + 'subName' => 'done', + ]; + } + $buttons[] = [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + 'js' => ['onclick' => "location.href='{$cancelURL}'; return false;"], + ]; + $this->addButtons($buttons); } if ($this->_action & CRM_Core_Action::DELETE) { + $this->assign('msg_title', $this->_values['msg_title']); return; } - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'title' => ts('Message Templates'), - 'url' => CRM_Utils_System::url('civicrm/admin/messageTemplates', - 'action=browse&reset=1' - ), - ), - ); + 'url' => CRM_Utils_System::url('civicrm/admin/messageTemplates', 'action=browse&reset=1'), + ], + ]; CRM_Utils_System::appendBreadCrumb($breadCrumb); $this->applyFilter('__ALL__', 'trim'); $this->add('text', 'msg_title', ts('Message Title'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_MessageTemplate', 'msg_title'), TRUE); - $options = array(ts('Compose On-screen'), ts('Upload Document')); + $options = [ts('Compose On-screen'), ts('Upload Document')]; $element = $this->addRadio('file_type', ts('Source'), $options); if ($this->_id) { $element->freeze(); @@ -201,12 +191,12 @@ public function buildQuickForm() { } else { $this->add('wysiwyg', 'msg_html', ts('HTML Message'), - array( + [ 'cols' => '80', 'rows' => '8', 'onkeyup' => "return verify(this)", 'preset' => 'civimail', - ) + ] ); } @@ -215,14 +205,13 @@ public function buildQuickForm() { ); $this->add('select', 'pdf_format_id', ts('PDF Page Format'), - array( + [ 'null' => ts('- default -'), - ) + CRM_Core_BAO_PdfFormat::getList(TRUE), FALSE + ] + CRM_Core_BAO_PdfFormat::getList(TRUE), FALSE ); $this->add('checkbox', 'is_active', ts('Enabled?')); - - $this->addFormRule(array(__CLASS__, 'formRule'), $this); + $this->addFormRule([__CLASS__, 'formRule'], $this); if ($this->_action & CRM_Core_Action::VIEW) { $this->freeze(); @@ -230,6 +219,26 @@ public function buildQuickForm() { } } + /** + * Restrict users access based on permission + * + * @param int $workflowId + */ + private function checkUserPermission($workflowId) { + if (isset($workflowId)) { + $canView = CRM_Core_Permission::check('edit system workflow message templates'); + } + else { + $canView = CRM_Core_Permission::check('edit user-driven message templates'); + } + + if (!$canView && !CRM_Core_Permission::check('edit message templates')) { + CRM_Core_Session::setStatus(ts('You do not have permission to view requested page.'), ts('Access Denied')); + $url = CRM_Utils_System::url('civicrm/admin/messageTemplates', "reset=1"); + CRM_Utils_System::redirect($url); + } + } + /** * Global form rule. * @@ -243,13 +252,11 @@ public function buildQuickForm() { * array of errors */ public static function formRule($params, $files, $self) { - $errors = array(); - // If user uploads non-document file other than odt/docx if (!empty($files['file_id']['tmp_name']) && array_search($files['file_id']['type'], CRM_Core_SelectValues::documentApplicationType()) == NULL ) { - $error['file_id'] = ts('Invalid document file format'); + $errors['file_id'] = ts('Invalid document file format'); } // If default is not set and no document file is uploaded elseif (empty($files['file_id']['tmp_name']) && !empty($params['file_type']) && !$self->_is_document) { @@ -257,7 +264,7 @@ public static function formRule($params, $files, $self) { $errors['file_id'] = ts('Please upload document'); } - return $errors; + return empty($errors) ? TRUE : $errors; } /** @@ -272,10 +279,8 @@ public function postProcess() { CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/messageTemplates', 'selectedChild=workflow&reset=1')); } else { - $params = array(); - // store the submitted values in an array - $params = $this->controller->exportValues($this->_name); + $params = $this->controller->exportValues(); if ($this->_action & CRM_Core_Action::UPDATE) { $params['id'] = $this->_id; @@ -286,7 +291,7 @@ public function postProcess() { unset($params['msg_text']); CRM_Utils_File::formatFile($params, 'file_id'); } - // delete related file refernces if html/text/pdf template are chosen over document + // delete related file references if html/text/pdf template are chosen over document elseif (!empty($this->_id)) { $entityFileDAO = new CRM_Core_DAO_EntityFile(); $entityFileDAO->entity_id = $this->_id; @@ -300,20 +305,24 @@ public function postProcess() { } } + $this->_workflow_id = CRM_Utils_Array::value('workflow_id', $this->_values); if ($this->_workflow_id) { $params['workflow_id'] = $this->_workflow_id; $params['is_active'] = TRUE; } $messageTemplate = CRM_Core_BAO_MessageTemplate::add($params); - CRM_Core_Session::setStatus(ts('The Message Template \'%1\' has been saved.', array(1 => $messageTemplate->msg_title)), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts('The Message Template \'%1\' has been saved.', [1 => $messageTemplate->msg_title]), ts('Saved'), 'success'); + if (isset($this->_submitValues['_qf_MessageTemplates_upload'])) { + // Save button was pressed + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/messageTemplates/add', "action=update&id={$messageTemplate->id}&reset=1")); + } + // Save and done button was pressed if ($this->_workflow_id) { CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/messageTemplates', 'selectedChild=workflow&reset=1')); } - else { - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/messageTemplates', 'selectedChild=user&reset=1')); - } + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/messageTemplates', 'selectedChild=user&reset=1')); } } diff --git a/CRM/Admin/Form/Navigation.php b/CRM/Admin/Form/Navigation.php index 1441b7d7a030..1d7ae860cb18 100644 --- a/CRM/Admin/Form/Navigation.php +++ b/CRM/Admin/Form/Navigation.php @@ -1,9 +1,9 @@ _id)) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; CRM_Core_BAO_Navigation::retrieve($params, $this->_defaults); } @@ -67,19 +68,22 @@ public function buildQuickForm() { ); $this->add('text', 'url', ts('Url'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Navigation', 'url')); - $permissions = array(); + + $this->add('text', 'icon', ts('Icon'), ['class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE]); + + $permissions = []; foreach (CRM_Core_Permission::basicPermissions(TRUE, TRUE) as $id => $vals) { - $permissions[] = array('id' => $id, 'label' => $vals[0], 'description' => (array) CRM_Utils_Array::value(1, $vals)); + $permissions[] = ['id' => $id, 'text' => $vals[0], 'description' => (array) CRM_Utils_Array::value(1, $vals)]; } - $this->add('text', 'permission', ts('Permission'), - array('placeholder' => ts('Unrestricted'), 'class' => 'huge', 'data-select-params' => json_encode(array('data' => array('results' => $permissions, 'text' => 'label')))) + $this->add('select2', 'permission', ts('Permission'), $permissions, FALSE, + ['placeholder' => ts('Unrestricted'), 'class' => 'huge', 'multiple' => TRUE] ); - $operators = array('AND' => ts('AND'), 'OR' => ts('OR')); + $operators = ['AND' => ts('AND'), 'OR' => ts('OR')]; $this->add('select', 'permission_operator', NULL, $operators); //make separator location configurable - $separator = array(ts('None'), ts('After menu element'), ts('Before menu element')); + $separator = [ts('None'), ts('After menu element'), ts('Before menu element')]; $this->add('select', 'has_separator', ts('Separator'), $separator); $active = $this->add('advcheckbox', 'is_active', ts('Enabled')); @@ -98,7 +102,7 @@ public function buildQuickForm() { $homeMenuId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Navigation', 'Home', 'id', 'name'); unset($parentMenu[$homeMenuId]); - $this->add('select', 'parent_id', ts('Parent'), array('' => ts('Top level')) + $parentMenu, FALSE, array('class' => 'crm-select2')); + $this->add('select', 'parent_id', ts('Parent'), ['' => ts('Top level')] + $parentMenu, FALSE, ['class' => 'crm-select2']); } } @@ -119,6 +123,10 @@ public function setDefaultValues() { // its ok if there is no element called is_active $defaults['is_active'] = ($this->_id) ? $this->_defaults['is_active'] : 1; + if (!empty($defaults['icon'])) { + $defaults['icon'] = trim(str_replace('crm-i', '', $defaults['icon'])); + } + return $defaults; } @@ -134,13 +142,17 @@ public function postProcess() { $params['current_parent_id'] = $this->_currentParentID; } + if (!empty($params['icon'])) { + $params['icon'] = 'crm-i ' . $params['icon']; + } + $navigation = CRM_Core_BAO_Navigation::add($params); // also reset navigation CRM_Core_BAO_Navigation::resetNavigation(); CRM_Core_Session::setStatus(ts('Menu \'%1\' has been saved.', - array(1 => $navigation->label) + [1 => $navigation->label] ), ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/OptionGroup.php b/CRM/Admin/Form/OptionGroup.php index c7622ff79070..8076680a6f81 100644 --- a/CRM/Admin/Form/OptionGroup.php +++ b/CRM/Admin/Form/OptionGroup.php @@ -1,9 +1,9 @@ addRule('name', ts('Name already exists in Database.'), 'objectExists', - array('CRM_Core_DAO_OptionGroup', $this->_id) + ['CRM_Core_DAO_OptionGroup', $this->_id] ); $this->add('text', @@ -78,15 +78,15 @@ public function buildQuickForm() { CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionGroup', 'description') ); - $this->addSelect('data_type', array('options' => CRM_Utils_Type::dataTypes()), TRUE); + $this->addSelect('data_type', ['options' => CRM_Utils_Type::dataTypes()], empty($this->_values['is_reserved'])); $element = $this->add('checkbox', 'is_active', ts('Enabled?')); if ($this->_action & CRM_Core_Action::UPDATE) { - if (in_array($this->_values['name'], array( + if (in_array($this->_values['name'], [ 'encounter_medium', 'case_type', 'case_status', - ))) { + ])) { static $caseCount = NULL; if (!isset($caseCount)) { $caseCount = CRM_Case_BAO_Case::caseCount(NULL, FALSE); @@ -96,8 +96,12 @@ public function buildQuickForm() { $element->freeze(); } } + + $this->add('checkbox', 'is_reserved', ts('Reserved?')); + $this->freeze('is_reserved'); + if (!empty($this->_values['is_reserved'])) { - $this->freeze(array('name', 'is_active')); + $this->freeze(['name', 'is_active', 'data_type']); } } @@ -110,23 +114,26 @@ public function buildQuickForm() { public function postProcess() { CRM_Utils_System::flushCache(); - $params = $this->exportValues(); if ($this->_action & CRM_Core_Action::DELETE) { CRM_Core_BAO_OptionGroup::del($this->_id); CRM_Core_Session::setStatus(ts('Selected option group has been deleted.'), ts('Record Deleted'), 'success'); } else { - - $params = $ids = array(); // store the submitted values in an array $params = $this->exportValues(); - if ($this->_action & CRM_Core_Action::UPDATE) { - $ids['optionGroup'] = $this->_id; + if ($this->_action & CRM_Core_Action::ADD) { + // If we are adding option group via UI it should not be marked reserved. + if (!isset($params['is_reserved'])) { + $params['is_reserved'] = 0; + } + } + elseif ($this->_action & CRM_Core_Action::UPDATE) { + $params['id'] = $this->_id; } - $optionGroup = CRM_Core_BAO_OptionGroup::add($params, $ids); - CRM_Core_Session::setStatus(ts('The Option Group \'%1\' has been saved.', array(1 => $optionGroup->name)), ts('Saved'), 'success'); + $optionGroup = CRM_Core_BAO_OptionGroup::add($params); + CRM_Core_Session::setStatus(ts('The Option Group \'%1\' has been saved.', [1 => $optionGroup->name]), ts('Saved'), 'success'); } } diff --git a/CRM/Admin/Form/Options.php b/CRM/Admin/Form/Options.php index 7dc2560376b1..824db5857e49 100644 --- a/CRM/Admin/Form/Options.php +++ b/CRM/Admin/Form/Options.php @@ -1,9 +1,9 @@ _gLabel = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $this->_gid, 'title'); + $this->_domainSpecific = in_array($this->_gName, CRM_Core_OptionGroup::$_domainIDGroups); $url = "civicrm/admin/options/{$this->_gName}"; $params = "reset=1"; if (($this->_action & CRM_Core_Action::DELETE) && - in_array($this->_gName, array('email_greeting', 'postal_greeting', 'addressee')) + in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee']) ) { // Don't allow delete if the option value belongs to addressee, postal or email greetings and is in use. $findValue = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'value'); - $queryParam = array(1 => array($findValue, 'Integer')); + $queryParam = [1 => [$findValue, 'Integer']]; $columnName = $this->_gName . "_id"; $sql = "SELECT count(id) FROM civicrm_contact WHERE " . $columnName . " = %1"; $isInUse = CRM_Core_DAO::singleValueQuery($sql, $queryParam); if ($isInUse) { $scriptURL = "" . ts('Learn more about a script that can automatically update contact addressee and greeting options.') . ""; - CRM_Core_Session::setStatus(ts('The selected %1 option has not been deleted because it is currently in use. Please update these contacts to use a different format before deleting this option. %2', array( - 1 => $this->_gLabel, - 2 => $scriptURL, - )), ts('Sorry'), 'error'); + CRM_Core_Session::setStatus(ts('The selected %1 option has not been deleted because it is currently in use. Please update these contacts to use a different format before deleting this option. %2', [ + 1 => $this->_gLabel, + 2 => $scriptURL, + ]), ts('Sorry'), 'error'); $redirect = CRM_Utils_System::url($url, $params); CRM_Utils_System::redirect($redirect); } @@ -115,19 +122,19 @@ public function setDefaultValues() { $defaults = parent::setDefaultValues(); // Default weight & value - $fieldValues = array('option_group_id' => $this->_gid); - foreach (array('weight', 'value') as $field) { + $fieldValues = ['option_group_id' => $this->_gid]; + foreach (['weight', 'value'] as $field) { if (empty($defaults[$field])) { $defaults[$field] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue', $fieldValues, $field); } } - //setDefault of contact types for email greeting, postal greeting, addressee, CRM-4575 - if (in_array($this->_gName, array( + // setDefault of contact types for email greeting, postal greeting, addressee, CRM-4575 + if (in_array($this->_gName, [ 'email_greeting', 'postal_greeting', 'addressee', - ))) { + ])) { $defaults['contactOptions'] = (CRM_Utils_Array::value('filter', $defaults)) ? $defaults['filter'] : NULL; } // CRM-11516 @@ -145,7 +152,7 @@ public function setDefaultValues() { */ public function buildQuickForm() { parent::buildQuickForm(); - $this->setPageTitle(ts('%1 Option', array(1 => $this->_gLabel))); + $this->setPageTitle(ts('%1 Option', [1 => $this->_gLabel])); if ($this->_action & CRM_Core_Action::DELETE) { return; @@ -172,35 +179,35 @@ public function buildQuickForm() { CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'value'), TRUE ); + $this->addRule('value', + ts('This Value already exists in the database for this option group. Please select a different Value.'), + 'optionExists', + ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'value', $this->_domainSpecific] + ); } else { - $this->add('text', 'icon', ts('Icon'), array('class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE)); + $this->add('text', 'icon', ts('Icon'), ['class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE]); } - if ($this->_gName == 'activity_status') { + if (in_array($this->_gName, ['activity_status', 'case_status'])) { $this->add('color', 'color', ts('Color')); } - if (!in_array($this->_gName, array( - 'email_greeting', - 'postal_greeting', - 'addressee', - )) && !$isReserved + if (!in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee']) + && !$isReserved ) { - $domainSpecificOptionGroups = array('from_email_address'); - $domainSpecific = in_array($this->_gName, $domainSpecificOptionGroups) ? TRUE : FALSE; $this->addRule('label', - ts('This Label already exists in the database for this option group. Please select a different Value.'), + ts('This Label already exists in the database for this option group. Please select a different Label.'), 'optionExists', - array('CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'label', $domainSpecific) + ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'label', $this->_domainSpecific] ); } if ($this->_gName == 'case_status') { - $classes = array( + $classes = [ 'Opened' => ts('Opened'), 'Closed' => ts('Closed'), - ); + ]; $grouping = $this->add('select', 'grouping', @@ -217,22 +224,27 @@ public function buildQuickForm() { $financialAccount = CRM_Contribute_PseudoConstant::financialAccount(NULL, key($accountType)); $this->add('select', 'financial_account_id', ts('Financial Account'), - array('' => ts('- select -')) + $financialAccount, + ['' => ts('- select -')] + $financialAccount, TRUE ); } - $required = FALSE; - if ($this->_gName == 'custom_search') { - $required = TRUE; + if ($this->_gName == 'activity_status') { + $this->add('select', + 'filter', + ts('Status Type'), + [ + CRM_Activity_BAO_Activity::INCOMPLETE => ts('Incomplete'), + CRM_Activity_BAO_Activity::COMPLETED => ts('Completed'), + CRM_Activity_BAO_Activity::CANCELLED => ts('Cancelled'), + ] + ); } - elseif ($this->_gName == 'redaction_rule' || $this->_gName == 'engagement_index') { - if ($this->_gName == 'redaction_rule') { - $this->add('checkbox', - 'filter', - ts('Regular Expression?') - ); - } + if ($this->_gName == 'redaction_rule') { + $this->add('checkbox', + 'filter', + ts('Regular Expression?') + ); } if ($this->_gName == 'participant_listing') { $this->add('text', @@ -245,8 +257,8 @@ public function buildQuickForm() { // Hard-coding attributes here since description is still stored as varchar and not text in the schema. dgg $this->add('wysiwyg', 'description', ts('Description'), - array('rows' => 4, 'cols' => 80), - $required + ['rows' => 4, 'cols' => 80], + $this->_gName == 'custom_search' ); } @@ -258,7 +270,7 @@ public function buildQuickForm() { ); } - $this->add('text', + $this->add('number', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'weight'), @@ -273,7 +285,7 @@ public function buildQuickForm() { (($this->_action & CRM_Core_Action::ADD) || !$isReserved) ) { $caseID = CRM_Core_Component::getComponentID('CiviCase'); - $components = array('' => ts('Contacts AND Cases'), $caseID => ts('Cases Only')); + $components = ['' => ts('Contacts AND Cases'), $caseID => ts('Cases Only')]; $this->add('select', 'component_id', ts('Component'), @@ -287,8 +299,8 @@ public function buildQuickForm() { $enabled->freeze(); } - //fix for CRM-3552, CRM-4575 - $showIsDefaultGroups = array( + // fix for CRM-3552, CRM-4575 + $showIsDefaultGroups = [ 'email_greeting', 'postal_greeting', 'addressee', @@ -300,33 +312,30 @@ public function buildQuickForm() { 'communication_style', 'soft_credit_type', 'website_type', - ); + ]; if (in_array($this->_gName, $showIsDefaultGroups)) { $this->assign('showDefault', TRUE); $this->add('checkbox', 'is_default', ts('Default Option?')); } - //get contact type for which user want to create a new greeting/addressee type, CRM-4575 - if (in_array($this->_gName, array( - 'email_greeting', - 'postal_greeting', - 'addressee', - )) && !$isReserved + // get contact type for which user want to create a new greeting/addressee type, CRM-4575 + if (in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee']) + && !$isReserved ) { - $values = array( + $values = [ 1 => ts('Individual'), 2 => ts('Household'), 3 => ts('Organization'), 4 => ts('Multiple Contact Merge'), - ); - $this->add('select', 'contactOptions', ts('Contact Type'), array('' => '-select-') + $values, TRUE); + ]; + $this->add('select', 'contactOptions', ts('Contact Type'), ['' => '-select-'] + $values, TRUE); $this->assign('showContactFilter', TRUE); } if ($this->_gName == 'participant_status') { // For Participant Status options, expose the 'filter' field to track which statuses are "Counted", and the Visibility field - $element = $this->add('checkbox', 'filter', ts('Counted?')); + $this->add('checkbox', 'filter', ts('Counted?')); $this->add('select', 'visibility_id', ts('Visibility'), CRM_Core_PseudoConstant::visibility()); } if ($this->_gName == 'participant_role') { @@ -334,7 +343,7 @@ public function buildQuickForm() { $this->add('checkbox', 'filter', ts('Counted?')); } - $this->addFormRule(array('CRM_Admin_Form_Options', 'formRule'), $this); + $this->addFormRule(['CRM_Admin_Form_Options', 'formRule'], $this); } /** @@ -349,18 +358,16 @@ public function buildQuickForm() { * * @return array * array of errors / empty array. + * @throws \CRM_Core_Exception */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; if ($self->_gName == 'case_status' && empty($fields['grouping'])) { $errors['grouping'] = ts('Status class is a required field'); } - if (in_array($self->_gName, array( - 'email_greeting', - 'postal_greeting', - 'addressee', - )) && empty($self->_defaultValues['is_reserved']) + if (in_array($self->_gName, ['email_greeting', 'postal_greeting', 'addressee']) + && empty($self->_defaultValues['is_reserved']) ) { $label = $fields['label']; $condition = " AND v.label = '{$label}' "; @@ -391,9 +398,9 @@ public static function formRule($fields, $files, $self) { $dataType = self::getOptionGroupDataType($self->_gName); if ($dataType && $self->_gName !== 'activity_type') { $validate = CRM_Utils_Type::validate($fields['value'], $dataType, FALSE); - if (!$validate) { + if ($validate === FALSE) { CRM_Core_Session::setStatus( - ts('Data Type of the value field for this option value does not match ' . $dataType), + ts('Data Type of the value field for this option value does not match %1.', [1 => $dataType]), ts('Value field Data Type mismatch')); } } @@ -419,39 +426,38 @@ public static function getOptionGroupDataType($optionGroupName) { */ public function postProcess() { if ($this->_action & CRM_Core_Action::DELETE) { - $fieldValues = array('option_group_id' => $this->_gid); - $wt = CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $this->_id, $fieldValues); + $fieldValues = ['option_group_id' => $this->_gid]; + CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $this->_id, $fieldValues); if (CRM_Core_BAO_OptionValue::del($this->_id)) { if ($this->_gName == 'phone_type') { CRM_Core_BAO_Phone::setOptionToNull(CRM_Utils_Array::value('value', $this->_defaultValues)); } - CRM_Core_Session::setStatus(ts('Selected %1 type has been deleted.', array(1 => $this->_gLabel)), ts('Record Deleted'), 'success'); + CRM_Core_Session::setStatus(ts('Selected %1 type has been deleted.', [1 => $this->_gLabel]), ts('Record Deleted'), 'success'); } else { - CRM_Core_Session::setStatus(ts('Selected %1 type has not been deleted.', array(1 => $this->_gLabel)), ts('Sorry'), 'error'); + CRM_Core_Session::setStatus(ts('Selected %1 type has not been deleted.', [1 => $this->_gLabel]), ts('Sorry'), 'error'); CRM_Utils_Weight::correctDuplicateWeights('CRM_Core_DAO_OptionValue', $fieldValues); } } else { - $ids = array(); $params = $this->exportValues(); // allow multiple defaults within group. - $allowMultiDefaults = array('email_greeting', 'postal_greeting', 'addressee', 'from_email_address'); + $allowMultiDefaults = ['email_greeting', 'postal_greeting', 'addressee', 'from_email_address']; if (in_array($this->_gName, $allowMultiDefaults)) { if ($this->_gName == 'from_email_address') { - $params['reset_default_for'] = array('domain_id' => CRM_Core_Config::domainID()); + $params['reset_default_for'] = ['domain_id' => CRM_Core_Config::domainID()]; } elseif ($filter = CRM_Utils_Array::value('contactOptions', $params)) { $params['filter'] = $filter; - $params['reset_default_for'] = array('filter' => "0, " . $params['filter']); + $params['reset_default_for'] = ['filter' => "0, " . $params['filter']]; } - //make sure we should has to have space, CRM-6977 + //make sure we only have a single space, CRM-6977 and dev/mail/15 if ($this->_gName == 'from_email_address') { - $params['label'] = str_replace('"<', '" <', $params['label']); + $params['label'] = $this->sanitizeFromEmailAddress($params['label']); } } @@ -469,28 +475,20 @@ public function postProcess() { $params['color'] = 'null'; } - $groupParams = array('name' => ($this->_gName)); - $optionValue = CRM_Core_OptionValue::addOptionValue($params, $groupParams, $this->_action, $this->_id); - - // CRM-11516 - if (!empty($params['financial_account_id'])) { - $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' ")); - $params = array( - 'entity_table' => 'civicrm_option_value', - 'entity_id' => $optionValue->id, - 'account_relationship' => $relationTypeId, - 'financial_account_id' => $params['financial_account_id'], - ); - CRM_Financial_BAO_FinancialTypeAccount::add($params); - } + $optionValue = CRM_Core_OptionValue::addOptionValue($params, $this->_gName, $this->_action, $this->_id); - CRM_Core_Session::setStatus(ts('The %1 \'%2\' has been saved.', array( - 1 => $this->_gLabel, - 2 => $optionValue->label, - )), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts('The %1 \'%2\' has been saved.', [ + 1 => $this->_gLabel, + 2 => $optionValue->label, + ]), ts('Saved'), 'success'); $this->ajaxResponse['optionValue'] = $optionValue->toArray(); } } + public function sanitizeFromEmailAddress($email) { + preg_match("/^\"(.*)\" *<([^@>]*@[^@>]*)>$/", $email, $parts); + return "\"{$parts[1]}\" <$parts[2]>"; + } + } diff --git a/CRM/Admin/Form/ParticipantStatusType.php b/CRM/Admin/Form/ParticipantStatusType.php index e55c74827122..fcaac917b1af 100644 --- a/CRM/Admin/Form/ParticipantStatusType.php +++ b/CRM/Admin/Form/ParticipantStatusType.php @@ -1,9 +1,9 @@ add('text', 'label', ts('Label'), $attributes['label'], TRUE); - $this->addSelect('class', array('required' => TRUE)); + $this->addSelect('class', ['required' => TRUE]); $this->add('checkbox', 'is_active', ts('Active?')); $this->add('checkbox', 'is_counted', ts('Counted?')); - $this->add('text', 'weight', ts('Order'), $attributes['weight'], TRUE); + $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE); - $this->addSelect('visibility_id', array('label' => ts('Visibility'), 'required' => TRUE)); + $this->addSelect('visibility_id', ['label' => ts('Visibility'), 'required' => TRUE]); $this->assign('id', $this->_id); } @@ -83,7 +83,7 @@ public function setDefaultValues() { } $this->_isReserved = CRM_Utils_Array::value('is_reserved', $defaults); if ($this->_isReserved) { - $this->freeze(array('name', 'class', 'is_active')); + $this->freeze(['name', 'class', 'is_active']); } return $defaults; } @@ -101,7 +101,7 @@ public function postProcess() { $formValues = $this->controller->exportValues($this->_name); - $params = array( + $params = [ 'name' => CRM_Utils_Array::value('name', $formValues), 'label' => CRM_Utils_Array::value('label', $formValues), 'class' => CRM_Utils_Array::value('class', $formValues), @@ -109,7 +109,7 @@ public function postProcess() { 'is_counted' => CRM_Utils_Array::value('is_counted', $formValues, FALSE), 'weight' => CRM_Utils_Array::value('weight', $formValues), 'visibility_id' => CRM_Utils_Array::value('visibility_id', $formValues), - ); + ]; // make sure a malicious POST does not change these on reserved statuses if ($this->_isReserved) { diff --git a/CRM/Admin/Form/PaymentProcessor.php b/CRM/Admin/Form/PaymentProcessor.php index 64fd697ea88a..d8b32939550b 100644 --- a/CRM/Admin/Form/PaymentProcessor.php +++ b/CRM/Admin/Form/PaymentProcessor.php @@ -1,9 +1,9 @@ _id) { - $this->_ppType = CRM_Utils_Request::retrieve('pp', 'String', $this, FALSE, NULL); - if (!$this->_ppType) { - $this->_ppType = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessor', + $this->_paymentProcessorType = CRM_Utils_Request::retrieve('pp', 'String', $this, FALSE, NULL); + if (!$this->_paymentProcessorType) { + $this->_paymentProcessorType = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessor', $this->_id, 'payment_processor_type_id' ); } - $this->set('pp', $this->_ppType); + $this->set('pp', $this->_paymentProcessorType); } else { - $this->_ppType = CRM_Utils_Request::retrieve('pp', 'String', $this, TRUE, NULL); + $this->_paymentProcessorType = CRM_Utils_Request::retrieve('pp', 'String', $this, TRUE, NULL); } - $this->assign('ppType', $this->_ppType); + $this->assign('ppType', $this->_paymentProcessorType); $ppTypeName = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType', - $this->_ppType, + $this->_paymentProcessorType, 'name' ); $this->assign('ppTypeName', $ppTypeName); - $this->_ppDAO = new CRM_Financial_DAO_PaymentProcessorType(); - $this->_ppDAO->id = $this->_ppType; + $this->_paymentProcessorDAO = new CRM_Financial_DAO_PaymentProcessorType(); + $this->_paymentProcessorDAO->id = $this->_paymentProcessorType; - $this->_ppDAO->find(TRUE); + $this->_paymentProcessorDAO->find(TRUE); if ($this->_id) { $refreshURL = CRM_Utils_System::url('civicrm/admin/paymentProcessor', @@ -101,60 +107,61 @@ public function preProcess() { $refreshURL .= "&civicrmDestination=$destination"; } + $this->refreshURL = $refreshURL; $this->assign('refreshURL', $refreshURL); - $this->assign('is_recur', $this->_ppDAO->is_recur); + $this->assign('is_recur', $this->_paymentProcessorDAO->is_recur); - $this->_fields = array( - array( + $this->_fields = [ + [ 'name' => 'user_name', - 'label' => $this->_ppDAO->user_name_label, - ), - array( + 'label' => $this->_paymentProcessorDAO->user_name_label, + ], + [ 'name' => 'password', - 'label' => $this->_ppDAO->password_label, - ), - array( + 'label' => $this->_paymentProcessorDAO->password_label, + ], + [ 'name' => 'signature', - 'label' => $this->_ppDAO->signature_label, - ), - array( + 'label' => $this->_paymentProcessorDAO->signature_label, + ], + [ 'name' => 'subject', - 'label' => $this->_ppDAO->subject_label, - ), - array( + 'label' => $this->_paymentProcessorDAO->subject_label, + ], + [ 'name' => 'url_site', 'label' => ts('Site URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - ); + ], + ]; - if ($this->_ppDAO->is_recur) { - $this->_fields[] = array( + if ($this->_paymentProcessorDAO->is_recur) { + $this->_fields[] = [ 'name' => 'url_recur', 'label' => ts('Recurring Payments URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ); + ]; } - if (!empty($this->_ppDAO->url_button_default)) { - $this->_fields[] = array( + if (!empty($this->_paymentProcessorDAO->url_button_default)) { + $this->_fields[] = [ 'name' => 'url_button', 'label' => ts('Button URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ); + ]; } - if (!empty($this->_ppDAO->url_api_default)) { - $this->_fields[] = array( + if (!empty($this->_paymentProcessorDAO->url_api_default)) { + $this->_fields[] = [ 'name' => 'url_api', 'label' => ts('API URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ); + ]; } } @@ -176,20 +183,23 @@ public function buildQuickForm($check = FALSE) { $attributes['name'], TRUE ); - $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', array( - 'CRM_Financial_DAO_PaymentProcessor', - $this->_id, - 'name', - CRM_Core_Config::domainID(), - )); + $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', [ + 'CRM_Financial_DAO_PaymentProcessor', + $this->_id, + 'name', + CRM_Core_Config::domainID(), + ]); $this->add('text', 'description', ts('Description'), $attributes['description'] ); - $types = CRM_Core_PseudoConstant::paymentProcessorType(); - $this->add('select', 'payment_processor_type_id', ts('Payment Processor Type'), $types, TRUE, - array('onchange' => "reload(true)") + $this->add('select', + 'payment_processor_type_id', + ts('Payment Processor Type'), + CRM_Financial_BAO_PaymentProcessor::buildOptions('payment_processor_type_id'), + TRUE, + ['onchange' => "reload(true)"] ); // Financial Account of account type asset CRM-11515 @@ -199,15 +209,15 @@ public function buildQuickForm($check = FALSE) { $this->assign('financialAccount', $fcount); } $this->add('select', 'financial_account_id', ts('Financial Account'), - array('' => ts('- select -')) + $financialAccount, + ['' => ts('- select -')] + $financialAccount, TRUE ); $this->addSelect('payment_instrument_id', - array( + [ 'entity' => 'contribution', 'label' => ts('Payment Method'), 'placeholder' => NULL, - ) + ] ); // is this processor active ? @@ -221,12 +231,12 @@ public function buildQuickForm($check = FALSE) { continue; } - $this->addField($field['name'], array('label' => $field['label'])); + $this->addField($field['name'], ['label' => $field['label']]); - $fieldSpec = civicrm_api3($this->getDefaultEntity(), 'getfield', array( + $fieldSpec = civicrm_api3($this->getDefaultEntity(), 'getfield', [ 'name' => $field['name'], 'action' => 'create', - )); + ]); $this->add($fieldSpec['values']['html']['type'], "test_{$field['name']}", $field['label'], $attributes[$field['name']] ); @@ -236,7 +246,7 @@ public function buildQuickForm($check = FALSE) { } } - $this->addFormRule(array('CRM_Admin_Form_PaymentProcessor', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_PaymentProcessor', 'formRule']); } /** @@ -249,7 +259,7 @@ public static function formRule($fields) { // make sure that at least one of live or test is present // and we have at least name and url_site // would be good to make this processor specific - $errors = array(); + $errors = []; if (!(self::checkSection($fields, $errors) || self::checkSection($fields, $errors, 'test') @@ -273,7 +283,7 @@ public static function formRule($fields) { * @return bool */ public static function checkSection(&$fields, &$errors, $section = NULL) { - $names = array('user_name'); + $names = ['user_name']; $present = FALSE; $allPresent = TRUE; @@ -301,23 +311,24 @@ public static function checkSection(&$fields, &$errors, $section = NULL) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!$this->_id) { $defaults['is_active'] = $defaults['is_default'] = 1; - $defaults['url_site'] = $this->_ppDAO->url_site_default; - $defaults['url_api'] = $this->_ppDAO->url_api_default; - $defaults['url_recur'] = $this->_ppDAO->url_recur_default; - $defaults['url_button'] = $this->_ppDAO->url_button_default; - $defaults['test_url_site'] = $this->_ppDAO->url_site_test_default; - $defaults['test_url_api'] = $this->_ppDAO->url_api_test_default; - $defaults['test_url_recur'] = $this->_ppDAO->url_recur_test_default; - $defaults['test_url_button'] = $this->_ppDAO->url_button_test_default; - $defaults['payment_instrument_id'] = $this->_ppDAO->payment_instrument_id; + $defaults['url_site'] = $this->_paymentProcessorDAO->url_site_default; + $defaults['url_api'] = $this->_paymentProcessorDAO->url_api_default; + $defaults['url_recur'] = $this->_paymentProcessorDAO->url_recur_default; + $defaults['url_button'] = $this->_paymentProcessorDAO->url_button_default; + $defaults['test_url_site'] = $this->_paymentProcessorDAO->url_site_test_default; + $defaults['test_url_api'] = $this->_paymentProcessorDAO->url_api_test_default; + $defaults['test_url_recur'] = $this->_paymentProcessorDAO->url_recur_test_default; + $defaults['test_url_button'] = $this->_paymentProcessorDAO->url_button_test_default; + $defaults['payment_instrument_id'] = $this->_paymentProcessorDAO->payment_instrument_id; // When user changes payment processor type, it is passed in via $this->_ppType so update defaults array. - if ($this->_ppType) { - $defaults['payment_processor_type_id'] = $this->_ppType; + if ($this->_paymentProcessorType) { + $defaults['payment_processor_type_id'] = $this->_paymentProcessorType; } + $defaults['financial_account_id'] = CRM_Financial_BAO_PaymentProcessor::getDefaultFinancialAccountID(); return $defaults; } $domainID = CRM_Core_Config::domainID(); @@ -330,15 +341,21 @@ public function setDefaultValues() { } CRM_Core_DAO::storeValues($dao, $defaults); - // When user changes payment processor type, it is passed in via $this->_ppType so update defaults array. - if ($this->_ppType) { - $defaults['payment_processor_type_id'] = $this->_ppType; + // If payment processor ID does not exist, $paymentProcessorName will be FALSE + $paymentProcessorName = CRM_Core_PseudoConstant::getName('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', $this->_paymentProcessorType); + if ($this->_paymentProcessorType && $paymentProcessorName) { + // When user changes payment processor type, it is passed in via $this->_ppType so update defaults array. + $defaults['payment_processor_type_id'] = $this->_paymentProcessorType; } + else { + CRM_Core_Session::setStatus('Payment Processor Type (ID=' . $this->_paymentProcessorType . ') not found. Did you disable the payment processor extension?', 'Missing Payment Processor', 'alert'); + } + $cards = json_decode(CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessor', $this->_id, 'accepted_credit_cards' ), TRUE); - $acceptedCards = array(); + $acceptedCards = []; if (!empty($cards)) { foreach ($cards as $card => $val) { $acceptedCards[$card] = 1; @@ -383,9 +400,26 @@ public function postProcess() { CRM_Core_DAO::executeQuery($query); } + if ($this->_paymentProcessorType !== $values['payment_processor_type_id']) { + // If we changed the payment processor type, need to update the object as well + $this->_paymentProcessorType = $values['payment_processor_type_id']; + $this->_paymentProcessorDAO = new CRM_Financial_DAO_PaymentProcessorType(); + $this->_paymentProcessorDAO->id = $values['payment_processor_type_id']; + $this->_paymentProcessorDAO->find(TRUE); + } $this->updatePaymentProcessor($values, $domainID, FALSE); $this->updatePaymentProcessor($values, $domainID, TRUE); - CRM_Core_Session::setStatus(ts('Payment processor %1 has been saved.', array(1 => "{$values['name']}")), ts('Saved'), 'success'); + + $processor = civicrm_api3('payment_processor', 'getsingle', ['name' => $values['name'], 'is_test' => 0]); + $errors = Civi\Payment\System::singleton()->checkProcessorConfig($processor); + if ($errors) { + CRM_Core_Session::setStatus($errors, 'Payment processor configuration invalid', 'error'); + Civi::log()->error('Payment processor configuration invalid: ' . $errors); + CRM_Core_Session::singleton()->pushUserContext($this->refreshURL); + } + else { + CRM_Core_Session::setStatus(ts('Payment processor %1 has been saved.', [1 => "{$values['name']}"]), ts('Saved'), 'success'); + } } /** @@ -397,12 +431,12 @@ public function postProcess() { */ public function updatePaymentProcessor(&$values, $domainID, $test) { if ($test) { - foreach (array('user_name', 'password', 'signature', 'url_site', 'url_recur', 'url_api', 'url_button', 'subject') as $field) { + foreach (['user_name', 'password', 'signature', 'url_site', 'url_recur', 'url_api', 'url_button', 'subject'] as $field) { $values[$field] = empty($values["test_{$field}"]) ? CRM_Utils_Array::value($field, $values) : $values["test_{$field}"]; } } if (!empty($values['accept_credit_cards'])) { - $creditCards = array(); + $creditCards = []; $accptedCards = array_keys($values['accept_credit_cards']); $creditCardTypes = CRM_Contribute_PseudoConstant::creditCard(); foreach ($creditCardTypes as $type => $val) { @@ -415,20 +449,20 @@ public function updatePaymentProcessor(&$values, $domainID, $test) { else { $creditCards = "NULL"; } - $params = array_merge(array( + $params = array_merge([ 'id' => $test ? $this->_testID : $this->_id, 'domain_id' => $domainID, 'is_test' => $test, 'is_active' => 0, 'is_default' => 0, - 'is_recur' => $this->_ppDAO->is_recur, - 'billing_mode' => $this->_ppDAO->billing_mode, - 'class_name' => $this->_ppDAO->class_name, - 'payment_type' => $this->_ppDAO->payment_type, - 'payment_instrument_id' => $this->_ppDAO->payment_instrument_id, + 'is_recur' => $this->_paymentProcessorDAO->is_recur, + 'billing_mode' => $this->_paymentProcessorDAO->billing_mode, + 'class_name' => $this->_paymentProcessorDAO->class_name, + 'payment_type' => $this->_paymentProcessorDAO->payment_type, + 'payment_instrument_id' => $this->_paymentProcessorDAO->payment_instrument_id, 'financial_account_id' => $values['financial_account_id'], 'accepted_credit_cards' => $creditCards, - ), $values); + ], $values); civicrm_api3('PaymentProcessor', 'create', $params); } diff --git a/CRM/Admin/Form/PaymentProcessorType.php b/CRM/Admin/Form/PaymentProcessorType.php index 9edb6eb7f51b..29c2e5fde6cc 100644 --- a/CRM/Admin/Form/PaymentProcessorType.php +++ b/CRM/Admin/Form/PaymentProcessorType.php @@ -1,9 +1,9 @@ _fields = array( - array( + $this->_fields = [ + [ 'name' => 'name', 'label' => ts('Name'), 'required' => TRUE, - ), - array( + ], + [ 'name' => 'title', 'label' => ts('Title'), 'required' => TRUE, - ), - array( + ], + [ 'name' => 'billing_mode', 'label' => ts('Billing Mode'), 'required' => TRUE, 'rule' => 'positiveInteger', 'msg' => ts('Enter a positive integer'), - ), - array( + ], + [ 'name' => 'description', 'label' => ts('Description'), - ), - array( + ], + [ 'name' => 'user_name_label', 'label' => ts('User Name Label'), - ), - array( + ], + [ 'name' => 'password_label', 'label' => ts('Password Label'), - ), - array( + ], + [ 'name' => 'signature_label', 'label' => ts('Signature Label'), - ), - array( + ], + [ 'name' => 'subject_label', 'label' => ts('Subject Label'), - ), - array( + ], + [ 'name' => 'class_name', 'label' => ts('PHP class name'), 'required' => TRUE, - ), - array( + ], + [ 'name' => 'url_site_default', 'label' => ts('Live Site URL'), 'required' => TRUE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_api_default', 'label' => ts('Live API URL'), 'required' => FALSE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_recur_default', 'label' => ts('Live Recurring Payments URL'), 'required' => TRUE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_button_default', 'label' => ts('Live Button URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_site_test_default', 'label' => ts('Test Site URL'), 'required' => TRUE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_api_test_default', 'label' => ts('Test API URL'), 'required' => FALSE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_recur_test_default', 'label' => ts('Test Recurring Payments URL'), 'required' => TRUE, 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - array( + ], + [ 'name' => 'url_button_test_default', 'label' => ts('Test Button URL'), 'rule' => 'url', 'msg' => ts('Enter a valid URL'), - ), - ); + ], + ]; } /** @@ -176,7 +176,7 @@ public function buildQuickForm($check = FALSE) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!$this->_id) { $defaults['is_active'] = $defaults['is_default'] = 1; @@ -203,7 +203,7 @@ public function setDefaultValues() { * Process the form submission. */ public function postProcess() { - CRM_Utils_System::flushCache('CRM_Financial_DAO_PaymentProcessorType'); + CRM_Utils_System::flushCache(); if ($this->_action & CRM_Core_Action::DELETE) { CRM_Financial_BAO_PaymentProcessorType::del($this->_id); diff --git a/CRM/Admin/Form/PdfFormats.php b/CRM/Admin/Form/PdfFormats.php index c984a4e4dc33..ea8e7c40c91c 100644 --- a/CRM/Admin/Form/PdfFormats.php +++ b/CRM/Admin/Form/PdfFormats.php @@ -1,7 +1,7 @@ add('text', 'name', ts('Name'), $attributes['name'], TRUE); - $this->add('text', 'description', ts('Description'), array('size' => CRM_Utils_Type::HUGE)); + $this->add('text', 'description', ts('Description'), ['size' => CRM_Utils_Type::HUGE]); $this->add('checkbox', 'is_default', ts('Is this PDF Page Format the default?')); $this->add('select', 'paper_size', ts('Paper Size'), - array( + [ 0 => ts('- default -'), - ) + CRM_Core_BAO_PaperSize::getList(TRUE), FALSE, - array('onChange' => "selectPaper( this.value );") + ] + CRM_Core_BAO_PaperSize::getList(TRUE), FALSE, + ['onChange' => "selectPaper( this.value );"] ); $this->add('static', 'paper_dimensions', NULL, ts('Width x Height')); $this->add('select', 'orientation', ts('Orientation'), CRM_Core_BAO_PdfFormat::getPageOrientations(), FALSE, - array('onChange' => "updatePaperDimensions();") + ['onChange' => "updatePaperDimensions();"] ); $this->add('select', 'metric', ts('Unit of Measure'), CRM_Core_BAO_PdfFormat::getUnits(), FALSE, - array('onChange' => "selectMetric( this.value );") + ['onChange' => "selectMetric( this.value );"] ); - $this->add('text', 'margin_left', ts('Left Margin'), array('size' => 8, 'maxlength' => 8), TRUE); - $this->add('text', 'margin_right', ts('Right Margin'), array('size' => 8, 'maxlength' => 8), TRUE); - $this->add('text', 'margin_top', ts('Top Margin'), array('size' => 8, 'maxlength' => 8), TRUE); - $this->add('text', 'margin_bottom', ts('Bottom Margin'), array('size' => 8, 'maxlength' => 8), TRUE); - $this->add('text', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_BAO_PdfFormat', 'weight'), TRUE); + $this->add('text', 'margin_left', ts('Left Margin'), ['size' => 8, 'maxlength' => 8], TRUE); + $this->add('text', 'margin_right', ts('Right Margin'), ['size' => 8, 'maxlength' => 8], TRUE); + $this->add('text', 'margin_top', ts('Top Margin'), ['size' => 8, 'maxlength' => 8], TRUE); + $this->add('text', 'margin_bottom', ts('Bottom Margin'), ['size' => 8, 'maxlength' => 8], TRUE); + $this->add('number', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_BAO_PdfFormat', 'weight'), TRUE); - $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', array( - 'CRM_Core_BAO_PdfFormat', - $this->_id, - )); + $this->addRule('name', ts('Name already exists in Database.'), 'objectExists', [ + 'CRM_Core_BAO_PdfFormat', + $this->_id, + ]); $this->addRule('margin_left', ts('Margin must be numeric'), 'numeric'); $this->addRule('margin_right', ts('Margin must be numeric'), 'numeric'); $this->addRule('margin_top', ts('Margin must be numeric'), 'numeric'); @@ -119,9 +120,9 @@ public function postProcess() { $bao = new CRM_Core_BAO_PdfFormat(); $bao->savePdfFormat($values, $this->_id); - $status = ts('Your new PDF Page Format titled %1 has been saved.', array(1 => $values['name']), ts('Saved'), 'success'); + $status = ts('Your new PDF Page Format titled %1 has been saved.', [1 => $values['name']], ts('Saved'), 'success'); if ($this->_action & CRM_Core_Action::UPDATE) { - $status = ts('Your PDF Page Format titled %1 has been updated.', array(1 => $values['name']), ts('Saved'), 'success'); + $status = ts('Your PDF Page Format titled %1 has been updated.', [1 => $values['name']], ts('Saved'), 'success'); } CRM_Core_Session::setStatus($status); } diff --git a/CRM/Admin/Form/Persistent.php b/CRM/Admin/Form/Persistent.php index 1f93a2c724ae..e62d3313bc0b 100644 --- a/CRM/Admin/Form/Persistent.php +++ b/CRM/Admin/Form/Persistent.php @@ -1,9 +1,9 @@ _indexID && ($this->_action & (CRM_Core_Action::UPDATE))) { - $params = array('id' => $this->_indexID); + $params = ['id' => $this->_indexID]; CRM_Core_BAO_Persistent::retrieve($params, $defaults); if (CRM_Utils_Array::value('is_config', $defaults) == 1) { $defaults['data'] = implode(',', $defaults['data']); @@ -71,23 +71,22 @@ public function setDefaultValues() { public function buildQuickForm() { $this->add('text', 'context', ts('Context:'), NULL, TRUE); $this->add('text', 'name', ts('Name:'), NULL, TRUE); - $this->add('textarea', 'data', ts('Data:'), array('rows' => 4, 'cols' => 50), TRUE); - $this->addButtons(array( - array( - 'type' => 'submit', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->add('textarea', 'data', ts('Data:'), ['rows' => 4, 'cols' => 50], TRUE); + $this->addButtons([ + [ + 'type' => 'submit', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } public function postProcess() { - $params = $ids = array(); + $params = $ids = []; $params = $this->controller->exportValues($this->_name); $params['is_config'] = $this->_config; diff --git a/CRM/Admin/Form/Preferences.php b/CRM/Admin/Form/Preferences.php index ab6519517da3..55578d404a53 100644 --- a/CRM/Admin/Form/Preferences.php +++ b/CRM/Admin/Form/Preferences.php @@ -1,9 +1,9 @@ _config->contact_id = $this->_contactID; } + $this->addFieldsDefinedInSettingsMetadata(); $settings = Civi::settings(); + // @todo replace this by defining all in settings. foreach ($this->_varNames as $groupName => $settingNames) { + CRM_Core_Error::deprecatedFunctionWarning('deprecated use of preferences form. This will be removed from core soon'); foreach ($settingNames as $settingName => $options) { $this->_config->$settingName = $settings->get($settingName); } @@ -98,23 +104,28 @@ public function preProcess() { * @return array */ public function setDefaultValues() { - $defaults = array(); + $this->_defaults = []; + $this->setDefaultsForMetadataDefinedFields(); foreach ($this->_varNames as $groupName => $settings) { + CRM_Core_Error::deprecatedFunctionWarning('deprecated use of preferences form. This will be removed from core soon'); foreach ($settings as $settingName => $settingDetails) { - $defaults[$settingName] = isset($this->_config->$settingName) ? $this->_config->$settingName : CRM_Utils_Array::value('default', $settingDetails, NULL); + $this->_defaults[$settingName] = isset($this->_config->$settingName) ? $this->_config->$settingName : CRM_Utils_Array::value('default', $settingDetails, NULL); } } - return $defaults; + return $this->_defaults; } /** + * @todo deprecate in favour of setting using metadata. + * * @param $defaults */ public function cbsDefaultValues(&$defaults) { foreach ($this->_varNames as $groupName => $groupValues) { + CRM_Core_Error::deprecatedFunctionWarning('deprecated use of preferences form. This will be removed from core soon'); foreach ($groupValues as $settingName => $fieldValue) { if ($fieldValue['html_type'] == 'checkboxes') { if (isset($this->_config->$settingName) && @@ -124,7 +135,7 @@ public function cbsDefaultValues(&$defaults) { substr($this->_config->$settingName, 1, -1) ); if (!empty($value)) { - $defaults[$settingName] = array(); + $defaults[$settingName] = []; foreach ($value as $n => $v) { $defaults[$settingName][$v] = 1; } @@ -142,10 +153,11 @@ public function buildQuickForm() { parent::buildQuickForm(); if (!empty($this->_varNames)) { + CRM_Core_Error::deprecatedFunctionWarning('deprecated use of preferences form. This will be removed from core soon'); foreach ($this->_varNames as $groupName => $groupValues) { $formName = CRM_Utils_String::titleToVar($groupName); $this->assign('formName', $formName); - $fields = array(); + $fields = []; foreach ($groupValues as $fieldName => $fieldValue) { $fields[$fieldName] = $fieldValue; @@ -154,10 +166,10 @@ public function buildQuickForm() { $this->addElement('text', $fieldName, $fieldValue['title'], - array( + [ 'maxlength' => 64, 'size' => 32, - ) + ] ); break; @@ -175,12 +187,12 @@ public function buildQuickForm() { break; case 'YesNo': - $this->addRadio($fieldName, $fieldValue['title'], array(0 => 'No', 1 => 'Yes'), NULL, '  '); + $this->addRadio($fieldName, $fieldValue['title'], [0 => 'No', 1 => 'Yes'], NULL, '  '); break; case 'checkboxes': $options = array_flip(CRM_Core_OptionGroup::values($fieldName, FALSE, FALSE, TRUE)); - $newOptions = array(); + $newOptions = []; foreach ($options as $key => $val) { $newOptions[$key] = $val; } @@ -188,7 +200,7 @@ public function buildQuickForm() { $fieldValue['title'], $newOptions, NULL, NULL, NULL, NULL, - array('  ', '  ', '
') + ['  ', '  ', '
'] ); break; @@ -196,7 +208,8 @@ public function buildQuickForm() { $this->addElement('select', $fieldName, $fieldValue['title'], - $fieldValue['option_values'] + $fieldValue['option_values'], + CRM_Utils_Array::value('attributes', $fieldValue) ); break; @@ -205,7 +218,7 @@ public function buildQuickForm() { break; case 'entity_reference': - $this->addEntityRef($fieldName, $fieldValue['title'], CRM_Utils_Array::value('options', $fieldValue, array())); + $this->addEntityRef($fieldName, $fieldValue['title'], CRM_Utils_Array::value('options', $fieldValue, [])); } } @@ -214,18 +227,17 @@ public function buildQuickForm() { } } - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); if ($this->_action == CRM_Core_Action::VIEW) { $this->freeze(); @@ -250,6 +262,14 @@ public function postProcess() { * Process the form submission. */ public function postProcessCommon() { + try { + $this->saveMetadataDefinedSettings($this->_params); + $this->filterParamsSetByMetadata($this->_params); + } + catch (CiviCRM_API3_Exception $e) { + CRM_Core_Session::setStatus($e->getMessage(), ts('Save Failed'), 'error'); + } + foreach ($this->_varNames as $groupName => $groupValues) { foreach ($groupValues as $settingName => $fieldValue) { switch ($fieldValue['html_type']) { @@ -282,7 +302,7 @@ public function postProcessCommon() { $value = CRM_Utils_Array::value($settingName, $this->_params); if ($value) { $value = trim($value); - $value = str_replace(array("\r\n", "\r"), "\n", $value); + $value = str_replace(["\r\n", "\r"], "\n", $value); } $this->_config->$settingName = $value; break; diff --git a/CRM/Admin/Form/Preferences/Address.php b/CRM/Admin/Form/Preferences/Address.php index b00f7b673953..059b5bc6ff5f 100644 --- a/CRM/Admin/Form/Preferences/Address.php +++ b/CRM/Admin/Form/Preferences/Address.php @@ -1,9 +1,9 @@ '- select -', - ) + CRM_Core_SelectValues::addressProvider(); - - $this->_varNames = array( - CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => array( - 'address_options' => array( - 'html_type' => 'checkboxes', - 'title' => ts('Address Fields'), - 'weight' => 1, - ), - 'address_format' => array( - 'html_type' => 'textarea', - 'title' => ts('Display Format'), - 'description' => NULL, - 'weight' => 2, - ), - 'mailing_format' => array( - 'html_type' => 'textarea', - 'title' => ts('Mailing Label Format'), - 'description' => NULL, - 'weight' => 3, - ), - 'hideCountryMailingLabels' => array( - 'html_type' => 'YesNo', - 'title' => ts('Hide Country in Mailing Labels when same as domain country'), - 'weight' => 4, - ), - ), - CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => array( - 'address_standardization_provider' => array( - 'html_type' => 'select', - 'title' => ts('Provider'), - 'option_values' => $addrProviders, - 'weight' => 5, - ), - 'address_standardization_userid' => array( - 'html_type' => 'text', - 'title' => ts('User ID'), - 'description' => NULL, - 'weight' => 6, - ), - 'address_standardization_url' => array( - 'html_type' => 'text', - 'title' => ts('Web Service URL'), - 'description' => NULL, - 'weight' => 7, - ), - ), - ); - - parent::preProcess(); - } - - /** - * @return array - */ - public function setDefaultValues() { - $defaults = array(); - $defaults['address_standardization_provider'] = $this->_config->address_standardization_provider; - $defaults['address_standardization_userid'] = $this->_config->address_standardization_userid; - $defaults['address_standardization_url'] = $this->_config->address_standardization_url; - - $this->addressSequence = isset($newSequence) ? $newSequence : ""; - - $defaults['address_format'] = $this->_config->address_format; - $defaults['mailing_format'] = $this->_config->mailing_format; - $defaults['hideCountryMailingLabels'] = $this->_config->hideCountryMailingLabels; - parent::cbsDefaultValues($defaults); - - return $defaults; - } + protected $_settings = [ + 'address_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'address_format' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'mailing_format' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'hideCountryMailingLabels' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'address_standardization_provider' => CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME, + 'address_standardization_userid' => CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME, + 'address_standardization_url' => CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME, + ]; /** * Build the form object. @@ -120,7 +52,7 @@ public function setDefaultValues() { public function buildQuickForm() { $this->applyFilter('__ALL__', 'trim'); - $this->addFormRule(array('CRM_Admin_Form_Preferences_Address', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Preferences_Address', 'formRule']); //get the tokens for Mailing Label field $tokens = CRM_Core_SelectValues::contactTokens(); @@ -142,13 +74,9 @@ public static function formRule($fields) { // make sure that there is a value for all of them // if any of them are set if ($p || $u || $w) { - if (!CRM_Utils_System::checkPHPVersion(5, FALSE)) { - $errors['_qf_default'] = ts('Address Standardization features require PHP version 5 or greater.'); - return $errors; - } if (!($p && $u && $w)) { - $errors['_qf_default'] = ts('You must provide values for all three Address Standarization fields.'); + $errors['_qf_default'] = ts('You must provide values for all three Address Standardization fields.'); return $errors; } } @@ -165,27 +93,29 @@ public function postProcess() { } $this->_params = $this->controller->exportValues($this->_name); + $addressOptions = CRM_Core_OptionGroup::values('address_options', TRUE); // check if county option has been set - $options = CRM_Core_OptionGroup::values('address_options', FALSE, FALSE, TRUE); - foreach ($options as $key => $title) { - if ($title == ts('County')) { - // check if the $key is present in $this->_params - if (isset($this->_params['address_options']) && - !empty($this->_params['address_options'][$key]) - ) { - // print a status message to the user if county table seems small - $countyCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_county"); - if ($countyCount < 10) { - CRM_Core_Session::setStatus(ts('You have enabled the County option. Please ensure you populate the county table in your CiviCRM Database. You can find extensions to populate counties in the CiviCRM Extensions Directory.', array(1 => 'href="' . CRM_Utils_System::url('civicrm/admin/extensions', array('reset' => 1), TRUE, 'extensions-addnew') . '"')), - ts('Populate counties'), - "info" - ); - } - } + if (CRM_Utils_Array::value($addressOptions['County'], $this->_params['address_options'])) { + $countyCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_county"); + if ($countyCount < 10) { + CRM_Core_Session::setStatus(ts('You have enabled the County option. Please ensure you populate the county table in your CiviCRM Database. You can find extensions to populate counties in the CiviCRM Extensions Directory.', [1 => 'href="' . CRM_Utils_System::url('civicrm/admin/extensions', ['reset' => 1], TRUE, 'extensions-addnew') . '"']), + ts('Populate counties'), + "info" + ); } } + // check that locale supports address parsing + if ( + CRM_Utils_Array::value($addressOptions['Street Address Parsing'], $this->_params['address_options']) && + !CRM_Core_BAO_Address::isSupportedParsingLocale() + ) { + $config = CRM_Core_Config::singleton(); + $locale = $config->lcMessages; + CRM_Core_Session::setStatus(ts('Default locale (%1) does not support street parsing. en_US locale will be used instead.', [1 => $locale]), ts('Unsupported Locale'), 'alert'); + } + $this->postProcessCommon(); } diff --git a/CRM/Admin/Form/Preferences/Campaign.php b/CRM/Admin/Form/Preferences/Campaign.php deleted file mode 100644 index 7c628073d743..000000000000 --- a/CRM/Admin/Form/Preferences/Campaign.php +++ /dev/null @@ -1,63 +0,0 @@ -_varNames = array( - CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => array( - 'tag_unconfirmed' => array( - 'html_type' => 'text', - 'title' => ts('Tag for Unconfirmed Petition Signers'), - 'weight' => 1, - 'description' => ts('If set, new contacts that are created when signing a petition are assigned a tag of this name.'), - ), - 'petition_contacts' => array( - 'html_type' => 'text', - 'title' => ts('Petition Signers Group'), - 'weight' => 2, - 'description' => ts('All contacts that have signed a CiviCampaign petition will be added to this group. The group will be created if it does not exist (it is required for email verification).'), - ), - ), - ); - - parent::preProcess(); - } - -} diff --git a/CRM/Admin/Form/Preferences/Contribute.php b/CRM/Admin/Form/Preferences/Contribute.php index 1c26bc51a2d4..6fc5e1dc17d7 100644 --- a/CRM/Admin/Form/Preferences/Contribute.php +++ b/CRM/Admin/Form/Preferences/Contribute.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, + 'update_contribution_on_membership_type_change' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'acl_financial_type' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'always_post_to_accounts_receivable' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'deferred_revenue_enabled' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'default_invoice_page' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'invoicing' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, - ); + ]; /** - * Process the form submission. + * Our standards for settings are to have a setting per value with defined metadata. + * + * Unfortunately the 'contribution_invoice_settings' has been added in non-compliance. + * We use this array to hack-handle. + * + * I think the best way forwards would be to covert to multiple individual settings. + * + * @var array */ - public function preProcess() { - $config = CRM_Core_Config::singleton(); - CRM_Utils_System::setTitle(ts('CiviContribute Component Settings')); - $this->_varNames = array( - CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => array( - 'invoice_prefix' => array( - 'html_type' => 'text', - 'title' => ts('Invoice Prefix'), - 'weight' => 1, - 'description' => ts('Enter prefix to be display on PDF for invoice'), - ), - 'credit_notes_prefix' => array( - 'html_type' => 'text', - 'title' => ts('Credit Notes Prefix'), - 'weight' => 2, - 'description' => ts('Enter prefix to be display on PDF for credit notes.'), - ), - 'due_date' => array( - 'html_type' => 'text', - 'title' => ts('Due Date'), - 'weight' => 3, - ), - 'due_date_period' => array( - 'html_type' => 'select', - 'title' => ts('For transmission'), - 'weight' => 4, - 'description' => ts('Select the interval for due date.'), - 'option_values' => array( - 'select' => ts('- select -'), - 'days' => ts('Days'), - 'months' => ts('Months'), - 'years' => ts('Years'), - ), - ), - 'notes' => array( - 'html_type' => 'wysiwyg', - 'title' => ts('Notes or Standard Terms'), - 'weight' => 5, - 'description' => ts('Enter note or message to be displayed on PDF invoice or credit notes '), - 'attributes' => array('rows' => 2, 'cols' => 40), - ), - 'is_email_pdf' => array( - 'html_type' => 'checkbox', - 'title' => ts('Automatically email invoice when user purchases online'), - 'weight' => 6, - ), - 'tax_term' => array( - 'html_type' => 'text', - 'title' => ts('Tax Term'), - 'weight' => 7, - ), - 'tax_display_settings' => array( - 'html_type' => 'select', - 'title' => ts('Tax Display Settings'), - 'weight' => 8, - 'option_values' => array( - 'Do_not_show' => ts('Do not show breakdown, only show total -i.e ' . - $config->defaultCurrencySymbol . '120.00'), - 'Inclusive' => ts('Show [tax term] inclusive price - i.e. ' . - $config->defaultCurrencySymbol . - '120.00 (includes [tax term] of ' . - $config->defaultCurrencySymbol . '20.00)'), - 'Exclusive' => ts('Show [tax term] exclusive price - i.e. ' . - $config->defaultCurrencySymbol . '100.00 + ' . - $config->defaultCurrencySymbol . '20.00 [tax term]'), - ), - ), - ), - ); - parent::preProcess(); - } + protected $invoiceSettings = []; /** * Build the form object. */ public function buildQuickForm() { - $htmlFields = array(); - foreach ($this->_settings as $setting => $group) { - $settingMetaData = civicrm_api3('setting', 'getfields', array('name' => $setting)); - $props = $settingMetaData['values'][$setting]; - if (isset($props['quick_form_type'])) { - $add = 'add' . $props['quick_form_type']; - if ($add == 'addElement') { - if (in_array($props['html_type'], array('checkbox', 'textarea'))) { - $this->add($props['html_type'], - $setting, - $props['title'] - ); - } - else { - if ($props['html_type'] == 'select') { - $functionName = CRM_Utils_Array::value('name', CRM_Utils_Array::value('pseudoconstant', $props)); - if ($functionName) { - $props['option_values'] = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::$functionName(); - } - } - $this->$add( - $props['html_type'], - $setting, - ts($props['title']), - CRM_Utils_Array::value($props['html_type'] == 'select' ? 'option_values' : 'html_attributes', $props, array()), - $props['html_type'] == 'select' ? CRM_Utils_Array::value('html_attributes', $props) : NULL - ); - } - } - elseif ($add == 'addMonthDay') { - $this->add('date', $setting, ts($props['title']), CRM_Core_SelectValues::date(NULL, 'M d')); - } - elseif ($add == 'addDate') { - $this->addDate($setting, ts($props['title']), FALSE, array('formatType' => $props['type'])); - } - else { - $this->$add($setting, ts($props['title'])); - } + parent::buildQuickForm(); + $config = CRM_Core_Config::singleton(); + $this->invoiceSettings = [ + 'invoice_prefix' => [ + 'html_type' => 'text', + 'title' => ts('Invoice Prefix'), + 'weight' => 1, + 'description' => ts('Enter prefix to be display on PDF for invoice'), + ], + 'credit_notes_prefix' => [ + 'html_type' => 'text', + 'title' => ts('Credit Notes Prefix'), + 'weight' => 2, + 'description' => ts('Enter prefix to be display on PDF for credit notes.'), + ], + 'due_date' => [ + 'html_type' => 'text', + 'title' => ts('Due Date'), + 'weight' => 3, + ], + 'due_date_period' => [ + 'html_type' => 'select', + 'title' => ts('For transmission'), + 'weight' => 4, + 'description' => ts('Select the interval for due date.'), + 'option_values' => [ + 'select' => ts('- select -'), + 'days' => ts('Days'), + 'months' => ts('Months'), + 'years' => ts('Years'), + ], + ], + 'notes' => [ + 'html_type' => 'wysiwyg', + 'title' => ts('Notes or Standard Terms'), + 'weight' => 5, + 'description' => ts('Enter note or message to be displayed on PDF invoice or credit notes '), + 'attributes' => ['rows' => 2, 'cols' => 40], + ], + 'is_email_pdf' => [ + 'html_type' => 'checkbox', + 'title' => ts('Automatically email invoice when user purchases online'), + 'weight' => 6, + 'description' => ts('Should a pdf invoice be emailed automatically?'), + ], + 'tax_term' => [ + 'html_type' => 'text', + 'title' => ts('Tax Term'), + 'weight' => 7, + ], + 'tax_display_settings' => [ + 'html_type' => 'select', + 'title' => ts('Tax Display Settings'), + 'weight' => 8, + 'option_values' => [ + 'Do_not_show' => ts('Do not show breakdown, only show total - i.e %1', [ + 1 => CRM_Utils_Money::format(120), + ]), + 'Inclusive' => ts('Show [tax term] inclusive price - i.e. %1', [ + 1 => ts('%1 (includes [tax term] of %2)', [1 => CRM_Utils_Money::format(120), 2 => CRM_Utils_Money::format(20)]), + ]), + 'Exclusive' => ts('Show [tax term] exclusive price - i.e. %1', [ + 1 => ts('%1 + %2 [tax term]', [1 => CRM_Utils_Money::format(120), 2 => CRM_Utils_Money::format(20)]), + ]), + ], + ], + ]; + + // @todo this is a faux metadata approach - we should be honest & add them correctly or find a way to make this + // compatible with our settings standards. + foreach ($this->invoiceSettings as $fieldName => $fieldValue) { + switch ($fieldValue['html_type']) { + case 'text': + $this->addElement('text', + $fieldName, + $fieldValue['title'], + [ + 'maxlength' => 64, + 'size' => 32, + ] + ); + break; + + case 'checkbox': + $this->add($fieldValue['html_type'], + $fieldName, + $fieldValue['title'] + ); + break; + + case 'select': + $this->addElement('select', + $fieldName, + $fieldValue['title'], + $fieldValue['option_values'], + CRM_Utils_Array::value('attributes', $fieldValue) + ); + break; + + case 'wysiwyg': + $this->add('wysiwyg', $fieldName, $fieldValue['title'], $fieldValue['attributes']); + break; } - $htmlFields[$setting] = ts($props['description']); } - $this->assign('htmlFields', $htmlFields); - parent::buildQuickForm(); + + $this->assign('htmlFields', $this->invoiceSettings); } /** @@ -174,18 +175,8 @@ public function buildQuickForm() { * default values are retrieved from the database */ public function setDefaultValues() { - $defaults = Civi::settings()->get('contribution_invoice_settings'); - //CRM-16691: Changes made related to settings of 'CVV'. - foreach (array('cvv_backoffice_required') as $setting) { - $settingMetaData = civicrm_api3('setting', 'getfields', array('name' => $setting)); - $defaults[$setting] = civicrm_api3('setting', 'getvalue', - array( - 'name' => $setting, - 'group' => CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, - 'default_value' => CRM_Utils_Array::value('default', $settingMetaData['values'][$setting]), - ) - ); - } + $defaults = parent::setDefaultValues(); + $defaults = array_merge($defaults, Civi::settings()->get('contribution_invoice_settings')); return $defaults; } @@ -195,37 +186,14 @@ public function setDefaultValues() { public function postProcess() { // store the submitted values in an array $params = $this->controller->exportValues($this->_name); - unset($params['qfKey']); - unset($params['entryURL']); - Civi::settings()->set('contribution_invoice_settings', $params); - - // to set default value for 'Invoices / Credit Notes' checkbox on display preferences - $values = CRM_Core_BAO_Setting::getItem("CiviCRM Preferences"); - $optionValues = CRM_Core_OptionGroup::values('user_dashboard_options', FALSE, FALSE, FALSE, NULL, 'name'); - $setKey = array_search('Invoices / Credit Notes', $optionValues); - - if (isset($params['invoicing'])) { - $value = array($setKey => $optionValues[$setKey]); - $setInvoice = CRM_Core_DAO::VALUE_SEPARATOR . - implode(CRM_Core_DAO::VALUE_SEPARATOR, array_keys($value)) . - CRM_Core_DAO::VALUE_SEPARATOR; - Civi::settings()->set('user_dashboard_options', $values['user_dashboard_options'] . $setInvoice); - } - else { - $setting = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($values['user_dashboard_options'], 1, -1)); - $invoiceKey = array_search($setKey, $setting); - if ($invoiceKey !== FALSE) { - unset($setting[$invoiceKey]); - } - $settingName = CRM_Core_DAO::VALUE_SEPARATOR . - implode(CRM_Core_DAO::VALUE_SEPARATOR, array_values($setting)) . - CRM_Core_DAO::VALUE_SEPARATOR; - Civi::settings()->set('user_dashboard_options', $settingName); - } - //CRM-16691: Changes made related to settings of 'CVV'. - $settings = array_intersect_key($params, array('cvv_backoffice_required' => 1)); - $result = civicrm_api3('setting', 'create', $settings); - CRM_Core_Session::setStatus(ts('Your changes have been saved.'), ts('Changes Saved'), "success"); + $invoiceParams = array_intersect_key($params, $this->invoiceSettings); + // This is a hack - invoicing is it's own setting but it is being used from invoice params + // too. This means that saving from api will not have the desired core effect. + // but we should fix that elsewhere - ie. stop abusing the settings + // and fix the code repetition associated with invoicing + $invoiceParams['invoicing'] = CRM_Utils_Array::value('invoicing', $params, 0); + Civi::settings()->set('contribution_invoice_settings', $invoiceParams); + parent::postProcess(); } } diff --git a/CRM/Admin/Form/Preferences/Display.php b/CRM/Admin/Form/Preferences/Display.php index c6d1d13fbd54..3e7b87a82509 100644 --- a/CRM/Admin/Form/Preferences/Display.php +++ b/CRM/Admin/Form/Preferences/Display.php @@ -1,9 +1,9 @@ _varNames = array( - CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME => array( - 'contact_view_options' => array( - 'html_type' => 'checkboxes', - 'title' => ts('Viewing Contacts'), - 'weight' => 1, - ), - 'contact_smart_group_display' => array( - 'html_type' => 'radio', - 'title' => ts('Viewing Smart Groups'), - 'weight' => 2, - ), - 'contact_edit_options' => array( - 'html_type' => 'checkboxes', - 'title' => ts('Editing Contacts'), - 'weight' => 3, - ), - 'advanced_search_options' => array( - 'html_type' => 'checkboxes', - 'title' => ts('Contact Search'), - 'weight' => 4, - ), - 'activity_assignee_notification' => array( - 'html_type' => 'checkbox', - 'title' => ts('Notify Activity Assignees'), - 'weight' => 5, - ), - 'activity_assignee_notification_ics' => array( - 'html_type' => 'checkbox', - 'title' => ts('Include ICal Invite to Activity Assignees'), - 'weight' => 6, - ), - 'contact_ajax_check_similar' => array( - 'html_type' => 'checkbox', - 'title' => ts('Check for Similar Contacts'), - 'weight' => 7, - ), - 'user_dashboard_options' => array( - 'html_type' => 'checkboxes', - 'title' => ts('Contact Dashboard'), - 'weight' => 8, - ), - 'display_name_format' => array( - 'html_type' => 'textarea', - 'title' => ts('Individual Display Name Format'), - 'weight' => 9, - ), - 'sort_name_format' => array( - 'html_type' => 'textarea', - 'title' => ts('Individual Sort Name Format'), - 'weight' => 10, - ), - 'editor_id' => array( - 'html_type' => NULL, - 'weight' => 11, - ), - 'ajaxPopupsEnabled' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable Popup Forms'), - 'weight' => 12, - ), - ), - ); - - parent::preProcess(); - } - /** - * @return array - */ - public function setDefaultValues() { - $defaults = parent::setDefaultValues(); - parent::cbsDefaultValues($defaults); - - if ($this->_config->display_name_format) { - $defaults['display_name_format'] = $this->_config->display_name_format; - } - if ($this->_config->sort_name_format) { - $defaults['sort_name_format'] = $this->_config->sort_name_format; - } - - return $defaults; - } + protected $_settings = [ + 'contact_view_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'contact_smart_group_display' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'contact_edit_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'advanced_search_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'user_dashboard_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'contact_ajax_check_similar' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'activity_assignee_notification' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'activity_assignee_notification_ics' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'do_not_notify_assignees_for' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'preserve_activity_tab_filter' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'editor_id' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'ajaxPopupsEnabled' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'display_name_format' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'sort_name_format' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'menubar_position' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'menubar_color' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'theme_backend' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'theme_frontend' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + ]; /** * Build the form object. */ public function buildQuickForm() { - $wysiwyg_options = CRM_Core_OptionGroup::values('wysiwyg_editor', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'); //changes for freezing the invoices/credit notes checkbox if invoicing is uncheck $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); - $this->assign('invoicing', $invoicing); - $extra = array(); + $this->assign('invoicing', CRM_Invoicing_Utils::isInvoicingEnabled()); - $this->addElement('select', 'editor_id', ts('WYSIWYG Editor'), $wysiwyg_options, $extra); $this->addElement('submit', 'ckeditor_config', ts('Configure CKEditor')); $editOptions = CRM_Core_OptionGroup::values('contact_edit_options', FALSE, FALSE, FALSE, 'AND v.filter = 0'); @@ -146,7 +77,7 @@ public function buildQuickForm() { $nameFields = CRM_Core_OptionGroup::values('contact_edit_options', FALSE, FALSE, FALSE, 'AND v.filter = 2'); $this->assign('nameFields', $nameFields); - $this->addElement('hidden', 'contact_edit_preferences', NULL, array('id' => 'contact_edit_preferences')); + $this->addElement('hidden', 'contact_edit_preferences', NULL, ['id' => 'contact_edit_preferences']); $optionValues = CRM_Core_OptionGroup::values('user_dashboard_options', FALSE, FALSE, FALSE, NULL, 'name'); $invoicesKey = array_search('Invoices / Credit Notes', $optionValues); @@ -175,8 +106,6 @@ public function postProcess() { CRM_Core_BAO_OptionValue::updateOptionWeights($opGroupId, array_flip($preferenceWeights)); } - $this->_config->editor_id = $this->_params['editor_id']; - $this->postProcessCommon(); // If "Configure CKEditor" button was clicked diff --git a/CRM/Admin/Form/Preferences/Event.php b/CRM/Admin/Form/Preferences/Event.php deleted file mode 100644 index 5d8d75a66038..000000000000 --- a/CRM/Admin/Form/Preferences/Event.php +++ /dev/null @@ -1,72 +0,0 @@ -_varNames = array( - CRM_Core_BAO_Setting::EVENT_PREFERENCES_NAME => array( - 'enable_cart' => array( - 'html_type' => 'checkbox', - 'title' => ts('Use Shopping Cart Style Event Registration'), - 'weight' => 1, - 'description' => ts('This feature allows users to register for more than one event at a time. When enabled, users will add event(s) to a "cart" and then pay for them all at once. Enabling this setting will affect online registration for all active events. The code is an alpha state, and you will potentially need to have developer resources to debug and fix sections of the codebase while testing and deploying it. %1', - array(1 => $docLink)), - ), - 'show_events' => array( - 'html_type' => 'select', - 'title' => ts('Dashboard entries'), - 'weight' => 2, - 'description' => ts('Configure how many events should be shown on the dashboard. This overrides the default value of 10 entries.'), - 'option_values' => array('' => ts('- select -')) + $optionValues + array(-1 => ts('show all')), - ), - ), - ); - - parent::preProcess(); - } - -} diff --git a/CRM/Admin/Form/Preferences/Mailing.php b/CRM/Admin/Form/Preferences/Mailing.php index 2b8af0a29a73..ef5e76fd0b2b 100644 --- a/CRM/Admin/Form/Preferences/Mailing.php +++ b/CRM/Admin/Form/Preferences/Mailing.php @@ -1,9 +1,9 @@ _varNames = array( - CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME => array( - 'profile_double_optin' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable Double Opt-in for Profile Group(s) field'), - 'weight' => 1, - 'description' => ts('When CiviMail is enabled, users who "subscribe" to a group from a profile Group(s) checkbox will receive a confirmation email. They must respond (opt-in) before they are added to the group.'), - ), - 'profile_add_to_group_double_optin' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable Double Opt-in for Profiles which use the "Add to Group" setting'), - 'weight' => 2, - 'description' => ts('When CiviMail is enabled and a profile uses the "Add to Group" setting, users who complete the profile form will receive a confirmation email. They must respond (opt-in) before they are added to the group.'), - ), - 'track_civimail_replies' => array( - 'html_type' => 'checkbox', - 'title' => ts('Track replies using VERP in Reply-To header'), - 'weight' => 3, - 'description' => ts('If checked, mailings will default to tracking replies using VERP-ed Reply-To.'), - ), - 'civimail_workflow' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable workflow support for CiviMail'), - 'weight' => 4, - 'description' => ts('Drupal-only. Rules module must be enabled (beta feature - use with caution).'), - ), - 'civimail_multiple_bulk_emails' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable multiple bulk email address for a contact.'), - 'weight' => 5, - 'description' => ts('CiviMail will deliver a copy of the email to each bulk email listed for the contact.'), - ), - 'civimail_server_wide_lock' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable global server wide lock for CiviMail'), - 'weight' => 6, - 'description' => NULL, - ), - 'include_message_id' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable CiviMail to generate Message-ID header'), - 'weight' => 7, - 'description' => NULL, - ), - 'write_activity_record' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable CiviMail to create activities on delivery'), - 'weight' => 8, - 'description' => NULL, - ), - 'disable_mandatory_tokens_check' => array( - 'html_type' => 'checkbox', - 'title' => ts('Disable check for mandatory tokens'), - 'weight' => 9, - 'description' => ts('Don\'t check for presence of mandatory tokens (domain address; unsubscribe/opt-out) before sending mailings. WARNING: Mandatory tokens are a safe-guard which facilitate compliance with the US CAN-SPAM Act. They should only be disabled if your organization adopts other mechanisms for compliance or if your organization is not subject to CAN-SPAM.'), - ), - 'dedupe_email_default' => array( - 'html_type' => 'checkbox', - 'title' => ts('CiviMail dedupes e-mail addresses by default'), - 'weight' => 10, - 'description' => NULL, - ), - 'hash_mailing_url' => array( - 'html_type' => 'checkbox', - 'title' => ts('Hashed Mailing URL\'s'), - 'weight' => 11, - 'description' => 'If enabled, a randomized hash key will be used to reference the mailing URL in the mailing.viewUrl token, instead of the mailing ID', - ), - ), - ); - parent::preProcess(); - } + protected $_settings = [ + 'profile_double_optin' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'profile_add_to_group_double_optin' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'track_civimail_replies' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'civimail_workflow' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'civimail_multiple_bulk_emails' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'civimail_server_wide_lock' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'include_message_id' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'write_activity_record' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'disable_mandatory_tokens_check' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'dedupe_email_default' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'hash_mailing_url' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'auto_recipient_rebuild' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + ]; public function postProcess() { - // check if mailing tab is enabled, if not prompt user to enable the tab if "write_activity_record" is disabled $params = $this->controller->exportValues($this->_name); if (empty($params['write_activity_record'])) { + // @todo use the setting onToggle & add an action rather than have specific form handling. + // see logging setting for eg. $existingViewOptions = Civi::settings()->get('contact_view_options'); - $displayValue = CRM_Core_OptionGroup::getValue('contact_view_options', 'CiviMail', 'name'); + $displayViewOptions = CRM_Core_OptionGroup::values('contact_view_options', TRUE, FALSE, FALSE, NULL, 'name'); + $displayValue = $displayViewOptions['CiviMail']; + $viewOptions = explode(CRM_Core_DAO::VALUE_SEPARATOR, $existingViewOptions); if (!in_array($displayValue, $viewOptions)) { diff --git a/CRM/Admin/Form/Preferences/Member.php b/CRM/Admin/Form/Preferences/Member.php index e1f68214d02d..f9b9b8fe4eed 100644 --- a/CRM/Admin/Form/Preferences/Member.php +++ b/CRM/Admin/Form/Preferences/Member.php @@ -1,9 +1,9 @@ _varNames = array( - CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME => array( - 'default_renewal_contribution_page' => array( - 'html_type' => 'select', - 'title' => ts('Default online membership renewal page'), - 'option_values' => array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::contributionPage(), - 'weight' => 1, - 'description' => ts('If you select a default online contribution page for self-service membership renewals, a "renew" link pointing to that page will be displayed on the Contact Dashboard for memberships which were entered offline. You will need to ensure that the membership block for the selected online contribution page includes any currently available memberships.'), - ), - ), - ); - parent::preProcess(); - } - - /** - * Build the form object. - */ - public function buildQuickForm() { - parent::buildQuickForm(); - } + protected $_settings = [ + 'default_renewal_contribution_page' => CRM_Core_BAO_Setting::MEMBER_PREFERENCES_NAME, + ]; } diff --git a/CRM/Admin/Form/Preferences/Multisite.php b/CRM/Admin/Form/Preferences/Multisite.php deleted file mode 100644 index 4aa58a961bf7..000000000000 --- a/CRM/Admin/Form/Preferences/Multisite.php +++ /dev/null @@ -1,81 +0,0 @@ -_varNames = array( - CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME => array( - 'is_enabled' => array( - 'html_type' => 'checkbox', - 'title' => ts('Enable Multi Site Configuration'), - 'weight' => 1, - 'description' => ts('Make CiviCRM aware of multiple domains. You should configure a domain group if enabled') . ' ' . $msDoc, - ), - /** Remove this checkbox until some one knows what this setting does - * 'uniq_email_per_site' => array( - * 'html_type' => 'checkbox', - * 'title' => ts('Ensure multi sites have a unique email per site'), - * 'weight' => 2, - * 'description' => NULL, - * ), - */ - 'domain_group_id' => array( - 'html_type' => 'entity_reference', - 'title' => ts('Domain Group'), - 'weight' => 3, - 'options' => array('entity' => 'group', 'select' => array('minimumInputLength' => 0)), - 'description' => ts('Contacts created on this site are added to this group'), - ), - /** Remove this checkbox until some one knows what this setting does - * 'event_price_set_domain_id' => array( - * 'html_type' => 'text', - * 'title' => ts('Domain for event price sets'), - * 'weight' => 4, - * 'description' => NULL, - * ), - */ - ), - ); - - parent::preProcess(); - } - -} diff --git a/CRM/Admin/Form/PreferencesDate.php b/CRM/Admin/Form/PreferencesDate.php index fcea91b7397b..95940ff6f8f7 100644 --- a/CRM/Admin/Form/PreferencesDate.php +++ b/CRM/Admin/Form/PreferencesDate.php @@ -1,9 +1,9 @@ add('select', 'date_format', ts('Format'), - array('' => ts('- default input format -')) + CRM_Core_SelectValues::getDatePluginInputFormats() + ['' => ts('- default input format -')] + CRM_Core_SelectValues::getDatePluginInputFormats() ); $this->add('select', 'time_format', ts('Time'), - array('' => ts('- none -')) + CRM_Core_SelectValues::getTimeFormats() + ['' => ts('- none -')] + CRM_Core_SelectValues::getTimeFormats() ); } $this->addRule('start', ts('Value must be an integer.'), 'integer'); $this->addRule('end', ts('Value must be an integer.'), 'integer'); // add a form rule - $this->addFormRule(array('CRM_Admin_Form_PreferencesDate', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_PreferencesDate', 'formRule']); } /** @@ -93,7 +93,7 @@ public function buildQuickForm() { * true otherwise */ public static function formRule($fields) { - $errors = array(); + $errors = []; if ($fields['name'] == 'activityDateTime' && !$fields['time_format']) { $errors['time_format'] = ts('Time is required for this format.'); @@ -129,7 +129,7 @@ public function postProcess() { CRM_Core_Resources::singleton()->resetCacheCode(); CRM_Core_Session::setStatus(ts("The date type '%1' has been saved.", - array(1 => $params['name']) + [1 => $params['name']] ), ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/RelationshipType.php b/CRM/Admin/Form/RelationshipType.php index b74ba659b8ed..b0c604c0103b 100644 --- a/CRM/Admin/Form/RelationshipType.php +++ b/CRM/Admin/Form/RelationshipType.php @@ -1,9 +1,9 @@ '' to hide) + * - not-auto-addable - this class will not attempt to add the field using addField. + * (this will be automatically set if the field does not have html in it's metadata + * or is not a core field on the form's entity). + * - help (option) add help to the field - e.g ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact']] + * - template - use a field specific template to render this field + * - required + * - is_freeze (field should be frozen). + * + * @var array + */ + protected $entityFields = []; + + /** + * Set entity fields to be assigned to the form. + */ + protected function setEntityFields() { + $this->entityFields = [ + 'label_a_b' => [ + 'name' => 'label_a_b', + 'description' => ts("Label for the relationship from Contact A to Contact B. EXAMPLE: Contact A is 'Parent of' Contact B."), + 'required' => TRUE, + ], + 'label_b_a' => [ + 'name' => 'label_b_a', + 'description' => ts("Label for the relationship from Contact B to Contact A. EXAMPLE: Contact B is 'Child of' Contact A. You may leave this blank for relationships where the name is the same in both directions (e.g. Spouse)."), + ], + 'description' => [ + 'name' => 'description', + 'description' => '', + ], + 'contact_types_a' => ['name' => 'contact_types_a', 'not-auto-addable' => TRUE], + 'contact_types_b' => ['name' => 'contact_types_b', 'not-auto-addable' => TRUE], + 'is_active' => ['name' => 'is_active'], + ]; + + self::setEntityFieldsMetadata(); + } + + /** + * Deletion message to be assigned to the form. + * + * @var string + */ + protected $deleteMessage; + + /** + * Explicitly declare the entity api name. + */ + public function getDefaultEntity() { + return 'RelationshipType'; + } + + /** + * Set the delete message. + * + * We do this from the constructor in order to do a translation. + */ + public function setDeleteMessage() { + $this->deleteMessage = ts('WARNING: Deleting this option will result in the loss of all Relationship records of this type.') . ts('This may mean the loss of a substantial amount of data, and the action cannot be undone.') . ts('Do you want to continue?'); + } + /** * Build the form object. */ public function buildQuickForm() { - parent::buildQuickForm(); - $this->setPageTitle(ts('Relationship Type')); + $isReserved = ($this->_id && CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $this->_id, 'is_reserved')); + $this->entityFields['is_active']['is_freeze'] = $isReserved; + self::buildQuickEntityForm(); if ($this->_action & CRM_Core_Action::DELETE) { return; } - $this->applyFilter('__ALL__', 'trim'); - - $this->add('text', 'label_a_b', ts('Relationship Label-A to B'), - CRM_Core_DAO::getAttribute('CRM_Contact_DAO_RelationshipType', 'label_a_b'), TRUE - ); $this->addRule('label_a_b', ts('Label already exists in Database.'), - 'objectExists', array('CRM_Contact_DAO_RelationshipType', $this->_id, 'label_a_b') - ); - - $this->add('text', 'label_b_a', ts('Relationship Label-B to A'), - CRM_Core_DAO::getAttribute('CRM_Contact_DAO_RelationshipType', 'label_b_a') + 'objectExists', ['CRM_Contact_DAO_RelationshipType', $this->_id, 'label_a_b'] ); - $this->addRule('label_b_a', ts('Label already exists in Database.'), - 'objectExists', array('CRM_Contact_DAO_RelationshipType', $this->_id, 'label_b_a') - ); - - $this->add('text', 'description', ts('Description'), - CRM_Core_DAO::getAttribute('CRM_Contact_DAO_RelationshipType', 'description') + 'objectExists', ['CRM_Contact_DAO_RelationshipType', $this->_id, 'label_b_a'] ); $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, TRUE, '__'); - - // add select for contact type - $contactTypeA = &$this->add('select', 'contact_types_a', ts('Contact Type A') . ' ', - array( - '' => ts('All Contacts'), - ) + $contactTypes - ); - $contactTypeB = &$this->add('select', 'contact_types_b', ts('Contact Type B') . ' ', - array( - '' => ts('All Contacts'), - ) + $contactTypes - ); - - $isActive = &$this->add('checkbox', 'is_active', ts('Enabled?')); - - //only selected field should be allow for edit, CRM-4888 - if ($this->_id && - CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $this->_id, 'is_reserved') - ) { - foreach (array('contactTypeA', 'contactTypeB', 'isActive') as $field) { - $$field->freeze(); + foreach (['contact_types_a' => ts('Contact Type A'), 'contact_types_b' => ts('Contact Type B')] as $name => $label) { + $element = $this->add('select', $name, $label . ' ', + [ + '' => ts('All Contacts'), + ] + $contactTypes + ); + if ($isReserved) { + $element->freeze(); } } @@ -97,8 +141,6 @@ public function buildQuickForm() { $this->freeze(); } - $this->assign('relationship_type_id', $this->_id); - } /** @@ -108,8 +150,8 @@ public function setDefaultValues() { if ($this->_action != CRM_Core_Action::DELETE && isset($this->_id) ) { - $defaults = $params = array(); - $params = array('id' => $this->_id); + $defaults = $params = []; + $params = ['id' => $this->_id]; $baoName = $this->_BAOName; $baoName::retrieve($params, $defaults); $defaults['contact_types_a'] = CRM_Utils_Array::value('contact_type_a', $defaults); @@ -117,7 +159,7 @@ public function setDefaultValues() { $defaults['contact_types_a'] .= '__' . $defaults['contact_sub_type_a']; } - $defaults['contact_types_b'] = $defaults['contact_type_b']; + $defaults['contact_types_b'] = CRM_Utils_Array::value('contact_type_b', $defaults); if (!empty($defaults['contact_sub_type_b'])) { $defaults['contact_types_b'] .= '__' . $defaults['contact_sub_type_b']; } @@ -137,14 +179,12 @@ public function postProcess() { CRM_Core_Session::setStatus(ts('Selected Relationship type has been deleted.'), ts('Record Deleted'), 'success'); } else { - $ids = array(); - // store the submitted values in an array $params = $this->exportValues(); $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE); if ($this->_action & CRM_Core_Action::UPDATE) { - $ids['relationshipType'] = $this->_id; + $params['id'] = $this->_id; } $cTypeA = CRM_Utils_System::explode('__', @@ -159,12 +199,22 @@ public function postProcess() { $params['contact_type_a'] = $cTypeA[0]; $params['contact_type_b'] = $cTypeB[0]; - $params['contact_sub_type_a'] = $cTypeA[1] ? $cTypeA[1] : 'NULL'; - $params['contact_sub_type_b'] = $cTypeB[1] ? $cTypeB[1] : 'NULL'; + $params['contact_sub_type_a'] = $cTypeA[1] ? $cTypeA[1] : 'null'; + $params['contact_sub_type_b'] = $cTypeB[1] ? $cTypeB[1] : 'null'; + + if (!strlen(trim(CRM_Utils_Array::value('label_b_a', $params)))) { + $params['label_b_a'] = CRM_Utils_Array::value('label_a_b', $params); + } + + if (empty($params['id'])) { + // Set name on created but don't update on update as the machine name is not exposed. + $params['name_b_a'] = $params['label_b_a']; + $params['name_a_b'] = $params['label_a_b']; + } - $result = CRM_Contact_BAO_RelationshipType::add($params, $ids); + $result = civicrm_api3('RelationshipType', 'create', $params); - $this->ajaxResponse['relationshipType'] = $result->toArray(); + $this->ajaxResponse['relationshipType'] = $result['values']; CRM_Core_Session::setStatus(ts('The Relationship Type has been saved.'), ts('Saved'), 'success'); } diff --git a/CRM/Admin/Form/ScheduleReminders.php b/CRM/Admin/Form/ScheduleReminders.php index 21f82dc32150..b827be6910df 100644 --- a/CRM/Admin/Form/ScheduleReminders.php +++ b/CRM/Admin/Form/ScheduleReminders.php @@ -1,9 +1,9 @@ _compId; + } + + /** + * @param mixed $compId + */ + public function setComponentID($compId) { + $this->_compId = $compId; + } + /** * Build the form object. */ @@ -50,56 +67,48 @@ public function buildQuickForm() { parent::buildQuickForm(); $this->_mappingID = $mappingID = NULL; $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->setContext(); + $isEvent = $this->getContext() == 'event'; - //CRM-16777: Don't provide access to administer schedule reminder page, with user that does not have 'administer CiviCRM' permission - if (empty($this->_context) && !CRM_Core_Permission::check('administer CiviCRM')) { - CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); - } - //CRM-16777: When user have ACLs 'edit' permission for specific event, do not give access to add, delete & updtae - //schedule reminder for other events. - else { - $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this); - if (!CRM_Event_BAO_Event::checkPermission($this->_compId, CRM_Core_Permission::EDIT)) { - CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); + if ($isEvent) { + $this->setComponentID(CRM_Utils_Request::retrieve('compId', 'Integer', $this)); + if (!CRM_Event_BAO_Event::checkPermission($this->getComponentID(), CRM_Core_Permission::EDIT)) { + throw new CRM_Core_Exception(ts('You do not have permission to access this page.')); } } + elseif (!CRM_Core_Permission::check('administer CiviCRM')) { + throw new CRM_Core_Exception(ts('You do not have permission to access this page.')); + } if ($this->_action & (CRM_Core_Action::DELETE)) { $reminderName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionSchedule', $this->_id, 'title'); - if ($this->_context == 'event') { - $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this); - } $this->assign('reminderName', $reminderName); return; } elseif ($this->_action & (CRM_Core_Action::UPDATE)) { $this->_mappingID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionSchedule', $this->_id, 'mapping_id'); - if ($this->_context == 'event') { - $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this); - } } - elseif (!empty($this->_context)) { - if ($this->_context == 'event') { - $this->_compId = CRM_Utils_Request::retrieve('compId', 'Integer', $this); - $isTemplate = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->_compId, 'is_template'); - $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings(array( - 'id' => $isTemplate ? CRM_Event_ActionMapping::EVENT_TPL_MAPPING_ID : CRM_Event_ActionMapping::EVENT_NAME_MAPPING_ID, - ))); - if ($mapping) { - $this->_mappingID = $mapping->getId(); - } - else { - CRM_Core_Error::fatal('Could not find mapping for event scheduled reminders.'); - } + if ($isEvent) { + $isTemplate = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $this->getComponentID(), 'is_template'); + $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings([ + 'id' => $isTemplate ? CRM_Event_ActionMapping::EVENT_TPL_MAPPING_ID : CRM_Event_ActionMapping::EVENT_NAME_MAPPING_ID, + ])); + if ($mapping) { + $this->_mappingID = $mapping->getId(); + } + else { + throw new CRM_Core_Exception('Could not find mapping for event scheduled reminders.'); } } - if (!empty($_POST) && !empty($_POST['entity']) && empty($this->_context)) { + if (!empty($_POST) && !empty($_POST['entity']) && empty($this->getContext())) { $mappingID = $_POST['entity'][0]; } elseif ($this->_mappingID) { $mappingID = $this->_mappingID; + if ($isEvent) { + $this->add('hidden', 'mappingID', $mappingID); + } } $this->add( @@ -120,21 +129,21 @@ public function buildQuickForm() { array_combine(array_keys($entityRecipientLabels), array_keys($entityRecipientLabels)) )); - if (empty($this->_context)) { + if (!$this->getContext()) { $sel = &$this->add( 'hierselect', 'entity', ts('Entity'), - array( + [ 'name' => 'entity[0]', 'style' => 'vertical-align: top;', - ) + ] ); - $sel->setOptions(array( + $sel->setOptions([ CRM_Utils_Array::collectMethod('getLabel', $mappings), CRM_Core_BAO_ActionSchedule::getAllEntityValueLabels(), CRM_Core_BAO_ActionSchedule::getAllEntityStatusLabels(), - )); + ]); if (is_a($sel->_elements[1], 'HTML_QuickForm_select')) { // make second selector a multi-select - @@ -152,31 +161,30 @@ public function buildQuickForm() { // Dig deeper - this code is sublimely stupid. $allEntityStatusLabels = CRM_Core_BAO_ActionSchedule::getAllEntityStatusLabels(); $options = $allEntityStatusLabels[$this->_mappingID][0]; - $attributes = array('multiple' => 'multiple', 'class' => 'crm-select2 huge', 'placeholder' => $options[0]); + $attributes = ['multiple' => 'multiple', 'class' => 'crm-select2 huge', 'placeholder' => $options[0]]; unset($options[0]); $this->add('select', 'entity', ts('Recipient(s)'), $options, TRUE, $attributes); - $this->assign('context', $this->_context); + $this->assign('context', $this->getContext()); } //get the frequency units. $this->_freqUnits = CRM_Core_SelectValues::getRecurringFrequencyUnits(); //reminder_interval - $this->add('number', 'start_action_offset', ts('When'), array('class' => 'six', 'min' => 0)); + $this->add('number', 'start_action_offset', ts('When'), ['class' => 'six', 'min' => 0]); $this->addRule('start_action_offset', ts('Value should be a positive number'), 'positiveInteger'); - $isActive = ts('Send email'); + $isActive = ts('Scheduled Reminder Active'); $recordActivity = ts('Record activity for automated email'); if ($providersCount) { $this->assign('sms', $providersCount); - $isActive = ts('Send email or SMS'); $recordActivity = ts('Record activity for automated email or SMS'); $options = CRM_Core_OptionGroup::values('msg_mode'); $this->add('select', 'mode', ts('Send as'), $options); $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc'); - $providerSelect = array(); + $providerSelect = []; foreach ($providers as $provider) { $providerSelect[$provider['id']] = $provider['title']; } @@ -184,18 +192,18 @@ public function buildQuickForm() { } foreach ($this->_freqUnits as $val => $label) { - $freqUnitsDisplay[$val] = ts('%1(s)', array(1 => $label)); + $freqUnitsDisplay[$val] = ts('%1(s)', [1 => $label]); } - $this->addDate('absolute_date', ts('Start Date'), FALSE, array('formatType' => 'mailing')); + $this->add('datepicker', 'absolute_date', ts('Start Date'), [], FALSE, ['time' => FALSE]); //reminder_frequency $this->add('select', 'start_action_unit', ts('Frequency'), $freqUnitsDisplay, TRUE); - $condition = array( + $condition = [ 'before' => ts('before'), 'after' => ts('after'), - ); + ]; //reminder_action $this->add('select', 'start_action_condition', ts('Action Condition'), $condition); @@ -204,15 +212,15 @@ public function buildQuickForm() { $this->addElement('checkbox', 'record_activity', $recordActivity); $this->addElement('checkbox', 'is_repeat', ts('Repeat'), - NULL, array('onchange' => "return showHideByValue('is_repeat',true,'repeatFields','table-row','radio',false);") + NULL, ['onchange' => "return showHideByValue('is_repeat',true,'repeatFields','table-row','radio',false);"] ); $this->add('select', 'repetition_frequency_unit', ts('every'), $freqUnitsDisplay); - $this->add('number', 'repetition_frequency_interval', ts('every'), array('class' => 'six', 'min' => 0)); + $this->add('number', 'repetition_frequency_interval', ts('every'), ['class' => 'six', 'min' => 0]); $this->addRule('repetition_frequency_interval', ts('Value should be a positive number'), 'positiveInteger'); $this->add('select', 'end_frequency_unit', ts('until'), $freqUnitsDisplay); - $this->add('number', 'end_frequency_interval', ts('until'), array('class' => 'six', 'min' => 0)); + $this->add('number', 'end_frequency_interval', ts('until'), ['class' => 'six', 'min' => 0]); $this->addRule('end_frequency_interval', ts('Value should be a positive number'), 'positiveInteger'); $this->add('select', 'end_action', ts('Repetition Condition'), $condition, TRUE); @@ -221,27 +229,27 @@ public function buildQuickForm() { $this->add('text', 'from_name', ts('From Name')); $this->add('text', 'from_email', ts('From Email')); - $recipientListingOptions = array(); + $recipientListingOptions = []; if ($mappingID) { - $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings(array( + $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings([ 'id' => $mappingID, - ))); + ])); } - $limitOptions = array('' => '-neither-', 1 => ts('Limit to'), 0 => ts('Also include')); + $limitOptions = ['' => '-neither-', 1 => ts('Limit to'), 0 => ts('Also include')]; - $recipientLabels = array('activity' => ts('Recipients'), 'other' => ts('Limit or Add Recipients')); + $recipientLabels = ['activity' => ts('Recipients'), 'other' => ts('Limit or Add Recipients')]; $this->assign('recipientLabels', $recipientLabels); - $this->add('select', 'limit_to', ts('Limit Options'), $limitOptions, FALSE, array('onChange' => "showHideByValue('limit_to','','recipient', 'select','select',true);")); + $this->add('select', 'limit_to', ts('Limit Options'), $limitOptions, FALSE, ['onChange' => "showHideByValue('limit_to','','recipient', 'select','select',true);"]); $this->add('select', 'recipient', $recipientLabels['other'], $entityRecipientLabels, - FALSE, array('onchange' => "showHideByValue('recipient','manual','recipientManual','table-row','select',false); showHideByValue('recipient','group','recipientGroup','table-row','select',false);") + FALSE, ['onchange' => "showHideByValue('recipient','manual','recipientManual','table-row','select',false); showHideByValue('recipient','group','recipientGroup','table-row','select',false);"] ); if (!empty($this->_submitValues['recipient_listing'])) { - if (!empty($this->_context)) { + if ($this->getContext()) { $recipientListingOptions = CRM_Core_BAO_ActionSchedule::getRecipientListing($this->_mappingID, $this->_submitValues['recipient']); } else { @@ -253,12 +261,12 @@ public function buildQuickForm() { } $this->add('select', 'recipient_listing', ts('Recipient Roles'), $recipientListingOptions, FALSE, - array('multiple' => TRUE, 'class' => 'crm-select2 huge', 'placeholder' => TRUE)); + ['multiple' => TRUE, 'class' => 'crm-select2 huge', 'placeholder' => TRUE]); - $this->addEntityRef('recipient_manual_id', ts('Manual Recipients'), array('multiple' => TRUE, 'create' => TRUE)); + $this->addEntityRef('recipient_manual_id', ts('Manual Recipients'), ['multiple' => TRUE, 'create' => TRUE]); $this->add('select', 'group_id', ts('Group'), - CRM_Core_PseudoConstant::nestedGroup('Mailing'), FALSE, array('class' => 'crm-select2 huge') + CRM_Core_PseudoConstant::nestedGroup('Mailing'), FALSE, ['class' => 'crm-select2 huge'] ); // multilingual only options @@ -268,14 +276,14 @@ public function buildQuickForm() { $smarty->assign('multilingual', $multilingual); $languages = CRM_Core_I18n::languages(TRUE); - $languageFilter = $languages + array(CRM_Core_I18n::NONE => ts('Contacts with no preferred language')); + $languageFilter = $languages + [CRM_Core_I18n::NONE => ts('Contacts with no preferred language')]; $element = $this->add('select', 'filter_contact_language', ts('Recipients language'), $languageFilter, FALSE, - array('multiple' => TRUE, 'class' => 'crm-select2', 'placeholder' => TRUE)); + ['multiple' => TRUE, 'class' => 'crm-select2', 'placeholder' => TRUE]); - $communicationLanguage = array( + $communicationLanguage = [ '' => ts('System default language'), CRM_Core_I18n::AUTO => ts('Follow recipient preferred language'), - ); + ]; $communicationLanguage = $communicationLanguage + $languages; $this->add('select', 'communication_language', ts('Communication language'), $communicationLanguage); } @@ -288,7 +296,7 @@ public function buildQuickForm() { $this->add('checkbox', 'is_active', $isActive); - $this->addFormRule(array('CRM_Admin_Form_ScheduleReminders', 'formRule'), $this); + $this->addFormRule(['CRM_Admin_Form_ScheduleReminders', 'formRule'], $this); $this->setPageTitle(ts('Scheduled Reminder')); } @@ -305,52 +313,73 @@ public function buildQuickForm() { * True if no errors, else array of errors */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; if ((array_key_exists(1, $fields['entity']) && $fields['entity'][1][0] === 0) || (array_key_exists(2, $fields['entity']) && $fields['entity'][2][0] == 0) ) { $errors['entity'] = ts('Please select appropriate value'); } + $mode = CRM_Utils_Array::value('mode', $fields, FALSE); if (!empty($fields['is_active']) && - CRM_Utils_System::isNull($fields['subject']) + CRM_Utils_System::isNull($fields['subject']) && (!$mode || $mode != 'SMS') ) { $errors['subject'] = ts('Subject is a required field.'); } if (!empty($fields['is_active']) && - CRM_Utils_System::isNull(trim(strip_tags($fields['html_message']))) + CRM_Utils_System::isNull(trim(strip_tags($fields['html_message']))) && (!$mode || $mode != 'SMS') ) { $errors['html_message'] = ts('The HTML message is a required field.'); } - if (empty($self->_context) && CRM_Utils_System::isNull(CRM_Utils_Array::value(1, $fields['entity']))) { + if (!empty($mode) && ($mode == 'SMS' || $mode == 'User_Preference') && !empty($fields['is_active']) && + CRM_Utils_System::isNull(trim(strip_tags($fields['sms_text_message']))) + ) { + $errors['sms_text_message'] = ts('The SMS message is a required field.'); + } + + if (empty($self->getContext()) && CRM_Utils_System::isNull(CRM_Utils_Array::value(1, $fields['entity']))) { $errors['entity'] = ts('Please select entity value'); } if (!CRM_Utils_System::isNull($fields['absolute_date'])) { - if (CRM_Utils_Date::format(CRM_Utils_Date::processDate($fields['absolute_date'], NULL)) < CRM_Utils_Date::format(date('Ymd'))) { + if ($fields['absolute_date'] < date('Y-m-d')) { $errors['absolute_date'] = ts('Absolute date cannot be earlier than the current time.'); } } - - $recipientKind = array( - 'participant_role' => array( + else { + if (CRM_Utils_System::isNull($fields['start_action_offset'])) { + $errors['start_action_offset'] = ts('Start Action Offset must be filled in or Absolute Date set'); + } + } + if (!CRM_Utils_Rule::email($fields['from_email']) && (!$mode || $mode != 'SMS')) { + $errors['from_email'] = ts('Please enter a valid email address.'); + } + $recipientKind = [ + 'participant_role' => [ 'name' => 'participant role', 'target_id' => 'recipient_listing', - ), - 'manual' => array( + ], + 'manual' => [ 'name' => 'recipient', 'target_id' => 'recipient_manual_id', - ), - ); + ], + ]; if ($fields['limit_to'] != '' && array_key_exists($fields['recipient'], $recipientKind) && empty($fields[$recipientKind[$fields['recipient']]['target_id']])) { - $errors[$recipientKind[$fields['recipient']]['target_id']] = ts('If "Also include" or "Limit to" are selected, you must specify at least one %1', array(1 => $recipientKind[$fields['recipient']]['name'])); + $errors[$recipientKind[$fields['recipient']]['target_id']] = ts('If "Also include" or "Limit to" are selected, you must specify at least one %1', [1 => $recipientKind[$fields['recipient']]['name']]); } - $actionSchedule = $self->parseActionSchedule($fields); - if ($actionSchedule->mapping_id) { - $mapping = CRM_Core_BAO_ActionSchedule::getMapping($actionSchedule->mapping_id); - CRM_Utils_Array::extend($errors, $mapping->validateSchedule($actionSchedule)); + //CRM-21523 + if (!empty($fields['is_repeat']) && + (empty($fields['repetition_frequency_interval']) || ($fields['end_frequency_interval'] == NULL)) + ) { + $errors['is_repeat'] = ts('If you are enabling repetition you must indicate the frequency and ending term.'); + } + + $self->_actionSchedule = $self->parseActionSchedule($fields); + if ($self->_actionSchedule->mapping_id) { + $mapping = CRM_Core_BAO_ActionSchedule::getMapping($self->_actionSchedule->mapping_id); + CRM_Utils_Array::extend($errors, $mapping->validateSchedule($self->_actionSchedule)); } if (!empty($errors)) { @@ -373,7 +402,7 @@ public function setDefaultValues() { $defaults = $this->_values; $entityValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('entity_value', $defaults)); $entityStatus = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('entity_status', $defaults)); - if (empty($this->_context)) { + if (empty($this->getContext())) { $defaults['entity'][0] = CRM_Utils_Array::value('mapping_id', $defaults); $defaults['entity'][1] = $entityValue; $defaults['entity'][2] = $entityStatus; @@ -381,10 +410,6 @@ public function setDefaultValues() { else { $defaults['entity'] = $entityStatus; } - if ($absoluteDate = CRM_Utils_Array::value('absolute_date', $defaults)) { - list($date, $time) = CRM_Utils_Date::setDateDefaults($absoluteDate); - $defaults['absolute_date'] = $date; - } if ($recipientListing = CRM_Utils_Array::value('recipient_listing', $defaults)) { $defaults['recipient_listing'] = explode(CRM_Core_DAO::VALUE_SEPARATOR, @@ -419,9 +444,9 @@ public function postProcess() { // delete reminder CRM_Core_BAO_ActionSchedule::del($this->_id); CRM_Core_Session::setStatus(ts('Selected Reminder has been deleted.'), ts('Record Deleted'), 'success'); - if ($this->_context == 'event' && $this->_compId) { + if ($this->getContext() == 'event' && $this->getComponentID()) { $url = CRM_Utils_System::url('civicrm/event/manage/reminder', - "reset=1&action=browse&id={$this->_compId}&component={$this->_context}&setTab=1" + "reset=1&action=browse&id=" . $this->getComponentID() . "&component=" . $this->getContext() . "&setTab=1" ); $session = CRM_Core_Session::singleton(); $session->pushUserContext($url); @@ -429,25 +454,29 @@ public function postProcess() { return; } $values = $this->controller->exportValues($this->getName()); - $bao = $this->parseActionSchedule($values)->save(); + if (empty($this->_actionSchedule)) { + $bao = $this->parseActionSchedule($values)->save(); + } + else { + $bao = $this->_actionSchedule->save(); + } // we need to set this on the form so that hooks can identify the created entity $this->set('id', $bao->id); - $bao->free(); $status = ts("Your new Reminder titled %1 has been saved.", - array(1 => "{$values['title']}") + [1 => "{$values['title']}"] ); if ($this->_action) { if ($this->_action & CRM_Core_Action::UPDATE) { $status = ts("Your Reminder titled %1 has been updated.", - array(1 => "{$values['title']}") + [1 => "{$values['title']}"] ); } - if ($this->_context == 'event' && $this->_compId) { - $url = CRM_Utils_System::url('civicrm/event/manage/reminder', "reset=1&action=browse&id={$this->_compId}&component={$this->_context}&setTab=1"); + if ($this->getContext() == 'event' && $this->getComponentID()) { + $url = CRM_Utils_System::url('civicrm/event/manage/reminder', "reset=1&action=browse&id=" . $this->getComponentID() . "&component=" . $this->getContext() . "&setTab=1"); $session = CRM_Core_Session::singleton(); $session->pushUserContext($url); } @@ -461,9 +490,9 @@ public function postProcess() { * @return CRM_Core_DAO_ActionSchedule */ public function parseActionSchedule($values) { - $params = array(); + $params = []; - $keys = array( + $keys = [ 'title', 'subject', 'absolute_date', @@ -474,14 +503,14 @@ public function parseActionSchedule($values) { 'sms_provider_id', 'from_name', 'from_email', - ); + ]; foreach ($keys as $key) { $params[$key] = CRM_Utils_Array::value($key, $values); } $params['is_repeat'] = CRM_Utils_Array::value('is_repeat', $values, 0); - $moreKeys = array( + $moreKeys = [ 'start_action_offset', 'start_action_unit', 'start_action_condition', @@ -492,12 +521,9 @@ public function parseActionSchedule($values) { 'end_frequency_interval', 'end_action', 'end_date', - ); + ]; - if ($absoluteDate = CRM_Utils_Array::value('absolute_date', $params)) { - $params['absolute_date'] = CRM_Utils_Date::processDate($absoluteDate); - } - else { + if (empty($params['absolute_date'])) { $params['absolute_date'] = 'null'; } foreach ($moreKeys as $mkey) { @@ -532,9 +558,9 @@ public function parseActionSchedule($values) { $params['group_id'] = $params['recipient_manual'] = $params['recipient_listing'] = 'null'; } - if (!empty($this->_mappingID) && !empty($this->_compId)) { + if (!empty($this->_mappingID) && !empty($this->getComponentID())) { $params['mapping_id'] = $this->_mappingID; - $params['entity_value'] = $this->_compId; + $params['entity_value'] = $this->getComponentID(); $params['entity_status'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values['entity']); } else { @@ -543,15 +569,15 @@ public function parseActionSchedule($values) { $params['limit_to'] = 1; } - $entity_value = CRM_Utils_Array::value(1, $values['entity'], array()); - $entity_status = CRM_Utils_Array::value(2, $values['entity'], array()); + $entity_value = CRM_Utils_Array::value(1, $values['entity'], []); + $entity_status = CRM_Utils_Array::value(2, $values['entity'], []); $params['entity_value'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $entity_value); $params['entity_status'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $entity_status); } $params['is_active'] = CRM_Utils_Array::value('is_active', $values, 0); - if (CRM_Utils_Array::value('is_repeat', $values) == 0) { + if (empty($values['is_repeat'])) { $params['repetition_frequency_unit'] = 'null'; $params['repetition_frequency_interval'] = 'null'; $params['end_frequency_unit'] = 'null'; @@ -561,7 +587,7 @@ public function parseActionSchedule($values) { } // multilingual options - $params['filter_contact_language'] = CRM_Utils_Array::value('filter_contact_language', $values, array()); + $params['filter_contact_language'] = CRM_Utils_Array::value('filter_contact_language', $values, []); $params['filter_contact_language'] = implode(CRM_Core_DAO::VALUE_SEPARATOR, $params['filter_contact_language']); $params['communication_language'] = CRM_Utils_Array::value('communication_language', $values, NULL); @@ -573,7 +599,7 @@ public function parseActionSchedule($values) { $params['name'] = CRM_Utils_String::munge($params['title'], '_', 64); } - $modePrefixes = array('Mail' => NULL, 'SMS' => 'SMS'); + $modePrefixes = ['Mail' => NULL, 'SMS' => 'SMS']; if ($params['mode'] == 'Email' || empty($params['sms_provider_id'])) { unset($modePrefixes['SMS']); @@ -584,17 +610,17 @@ public function parseActionSchedule($values) { //TODO: handle postprocessing of SMS and/or Email info based on $modePrefixes - $composeFields = array( + $composeFields = [ 'template', 'saveTemplate', 'updateTemplate', 'saveTemplateName', - ); + ]; $msgTemplate = NULL; //mail template is composed foreach ($modePrefixes as $prefix) { - $composeParams = array(); + $composeParams = []; foreach ($composeFields as $key) { $key = $prefix . $key; if (!empty($values[$key])) { @@ -603,19 +629,19 @@ public function parseActionSchedule($values) { } if (!empty($composeParams[$prefix . 'updateTemplate'])) { - $templateParams = array('is_active' => TRUE); + $templateParams = ['is_active' => TRUE]; if ($prefix == 'SMS') { - $templateParams += array( + $templateParams += [ 'msg_text' => $params['sms_body_text'], 'is_sms' => TRUE, - ); + ]; } else { - $templateParams += array( + $templateParams += [ 'msg_text' => $params['body_text'], 'msg_html' => $params['body_html'], 'msg_subject' => $params['subject'], - ); + ]; } $templateParams['id'] = $values[$prefix . 'template']; @@ -623,19 +649,19 @@ public function parseActionSchedule($values) { } if (!empty($composeParams[$prefix . 'saveTemplate'])) { - $templateParams = array('is_active' => TRUE); + $templateParams = ['is_active' => TRUE]; if ($prefix == 'SMS') { - $templateParams += array( + $templateParams += [ 'msg_text' => $params['sms_body_text'], 'is_sms' => TRUE, - ); + ]; } else { - $templateParams += array( + $templateParams += [ 'msg_text' => $params['body_text'], 'msg_html' => $params['body_html'], 'msg_subject' => $params['subject'], - ); + ]; } $templateParams['msg_title'] = $composeParams[$prefix . 'saveTemplateName']; diff --git a/CRM/Admin/Form/Setting.php b/CRM/Admin/Form/Setting.php index 671c8fe7ed3f..1a6f0304e3e5 100644 --- a/CRM/Admin/Form/Setting.php +++ b/CRM/Admin/Form/Setting.php @@ -1,9 +1,9 @@ _defaults) { - $this->_defaults = array(); - $formArray = array('Component', 'Localization'); + $this->_defaults = []; + $formArray = ['Component', 'Localization']; $formMode = FALSE; if (in_array($this->_name, $formArray)) { $formMode = TRUE; } - CRM_Core_BAO_ConfigSetting::retrieve($this->_defaults); + $this->setDefaultsForMetadataDefinedFields(); - // we can handle all the ones defined in the metadata here. Others to be converted - foreach ($this->_settings as $setting => $group) { - $this->_defaults[$setting] = civicrm_api('setting', 'getvalue', array( - 'version' => 3, - 'name' => $setting, - 'group' => $group, - ) - ); - } - - $this->_defaults['contact_autocomplete_options'] = self::getAutocompleteContactSearch(); - $this->_defaults['contact_reference_options'] = self::getAutocompleteContactReference(); + // @todo these should be retrievable from the above function. $this->_defaults['enableSSL'] = Civi::settings()->get('enableSSL'); $this->_defaults['verifySSL'] = Civi::settings()->get('verifySSL'); + $this->_defaults['environment'] = CRM_Core_Config::environment(); $this->_defaults['enableComponents'] = Civi::settings()->get('enable_components'); } @@ -78,88 +72,24 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - $session = CRM_Core_Session::singleton(); - $session->pushUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); - $args = func_get_args(); - $check = reset($args); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); - - $descriptions = array(); - foreach ($this->_settings as $setting => $group) { - $settingMetaData = civicrm_api('setting', 'getfields', array('version' => 3, 'name' => $setting)); - $props = $settingMetaData['values'][$setting]; - if (isset($props['quick_form_type'])) { - if (isset($props['pseudoconstant'])) { - $options = civicrm_api3('Setting', 'getoptions', array( - 'field' => $setting, - )); - } - else { - $options = NULL; - } - - $add = 'add' . $props['quick_form_type']; - if ($add == 'addElement') { - $this->$add( - $props['html_type'], - $setting, - ts($props['title']), - ($options !== NULL) ? $options['values'] : CRM_Utils_Array::value('html_attributes', $props, array()), - ($options !== NULL) ? CRM_Utils_Array::value('html_attributes', $props, array()) : NULL - ); - } - elseif ($add == 'addSelect') { - $this->addElement('select', $setting, ts($props['title']), $options['values'], CRM_Utils_Array::value('html_attributes', $props)); - } - elseif ($add == 'addCheckBox') { - $this->addCheckBox($setting, ts($props['title']), $options['values'], NULL, CRM_Utils_Array::value('html_attributes', $props), NULL, NULL, array('  ')); - } - elseif ($add == 'addChainSelect') { - $this->addChainSelect($setting, array( - 'label' => ts($props['title']), - )); - } - elseif ($add == 'addMonthDay') { - $this->add('date', $setting, ts($props['title']), CRM_Core_SelectValues::date(NULL, 'M d')); - } - else { - $this->$add($setting, ts($props['title'])); - } - // Migrate to using an array as easier in smart... - $descriptions[$setting] = ts($props['description']); - $this->assign("{$setting}_description", ts($props['description'])); - if ($setting == 'max_attachments') { - //temp hack @todo fix to get from metadata - $this->addRule('max_attachments', ts('Value should be a positive number'), 'positiveInteger'); - } - if ($setting == 'maxFileSize') { - //temp hack - $this->addRule('maxFileSize', ts('Value should be a positive number'), 'positiveInteger'); - } - - } + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + + $this->addFieldsDefinedInSettingsMetadata(); + + if ($this->includesReadOnlyFields) { + CRM_Core_Session::setStatus(ts("Some fields are loaded as 'readonly' as they have been set (overridden) in civicrm.settings.php."), '', 'info', ['expires' => 0]); } - $this->assign('setting_descriptions', $descriptions); - } - - /** - * Get default entity. - * - * @return string - */ - public function getDefaultEntity() { - return 'Setting'; } /** @@ -178,54 +108,33 @@ public function postProcess() { * @todo Document what I do. * * @param array $params + * @throws \CRM_Core_Exception */ public function commonProcess(&$params) { - // save autocomplete search options - if (!empty($params['contact_autocomplete_options'])) { - Civi::settings()->set('contact_autocomplete_options', - CRM_Utils_Array::implodePadded(array_keys($params['contact_autocomplete_options']))); - unset($params['contact_autocomplete_options']); - } - - // save autocomplete contact reference options - if (!empty($params['contact_reference_options'])) { - Civi::settings()->set('contact_reference_options', - CRM_Utils_Array::implodePadded(array_keys($params['contact_reference_options']))); - unset($params['contact_reference_options']); - } - - // save components to be enabled - if (array_key_exists('enableComponents', $params)) { - civicrm_api3('setting', 'create', array( - 'enable_components' => $params['enableComponents'], - )); - unset($params['enableComponents']); - } - - foreach (array('verifySSL', 'enableSSL') as $name) { + foreach (['verifySSL', 'enableSSL'] as $name) { if (isset($params[$name])) { Civi::settings()->set($name, $params[$name]); unset($params[$name]); } } - $settings = array_intersect_key($params, $this->_settings); - $result = civicrm_api('setting', 'create', $settings + array('version' => 3)); - foreach ($settings as $setting => $settingGroup) { - //@todo array_diff this - unset($params[$setting]); + try { + $this->saveMetadataDefinedSettings($params); } - if (!empty($result['error_message'])) { - CRM_Core_Session::setStatus($result['error_message'], ts('Save Failed'), 'error'); + catch (CiviCRM_API3_Exception $e) { + CRM_Core_Session::setStatus($e->getMessage(), ts('Save Failed'), 'error'); } - //CRM_Core_BAO_ConfigSetting::create($params); + $this->filterParamsSetByMetadata($params); + $params = CRM_Core_BAO_ConfigSetting::filterSkipVars($params); if (!empty($params)) { - CRM_Core_Error::fatal('Unrecognized setting. This may be a config field which has not been properly migrated to a setting. (' . implode(', ', array_keys($params)) . ')'); + throw new CRM_Core_Exception('Unrecognized setting. This may be a config field which has not been properly migrated to a setting. (' . implode(', ', array_keys($params)) . ')'); } CRM_Core_Config::clearDBCache(); + // This doesn't make a lot of sense to me, but it maintains pre-existing behavior. + Civi::cache('session')->clear(); CRM_Utils_System::flushCache(); CRM_Core_Resources::singleton()->resetCacheCode(); @@ -238,57 +147,6 @@ public function rebuildMenu() { // rebuild menu items CRM_Core_Menu::store(); - - // also delete the IDS file so we can write a new correct one on next load - $configFile = $config->uploadDir . 'Config.IDS.ini'; - @unlink($configFile); - } - - /** - * Ugh, this shouldn't exist. - * - * Get the selected values of "contact_reference_options" formatted for checkboxes. - * - * @return array - */ - public static function getAutocompleteContactReference() { - $cRlist = array_flip(CRM_Core_OptionGroup::values('contact_reference_options', - FALSE, FALSE, TRUE, NULL, 'name' - )); - $cRlistEnabled = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, - 'contact_reference_options' - ); - $cRSearchFields = array(); - if (!empty($cRlist) && !empty($cRlistEnabled)) { - $cRSearchFields = array_combine($cRlist, $cRlistEnabled); - } - return array( - '1' => 1, - ) + $cRSearchFields; - } - - /** - * Ugh, this shouldn't exist. - * - * Get the selected values of "contact_autocomplete_options" formatted for checkboxes. - * - * @return array - */ - public static function getAutocompleteContactSearch() { - $list = array_flip(CRM_Core_OptionGroup::values('contact_autocomplete_options', - FALSE, FALSE, TRUE, NULL, 'name' - )); - $listEnabled = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, - 'contact_autocomplete_options' - ); - $autoSearchFields = array(); - if (!empty($list) && !empty($listEnabled)) { - $autoSearchFields = array_combine($list, $listEnabled); - } - //Set defaults for autocomplete and contact reference options - return array( - '1' => 1, - ) + $autoSearchFields; } } diff --git a/CRM/Admin/Form/Setting/Case.php b/CRM/Admin/Form/Setting/Case.php new file mode 100644 index 000000000000..242c93adcdcd --- /dev/null +++ b/CRM/Admin/Form/Setting/Case.php @@ -0,0 +1,54 @@ + CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'civicaseAllowMultipleClients' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'civicaseNaturalActivityTypeSort' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'civicaseActivityRevisions' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + ]; + + /** + * Build the form object. + */ + public function buildQuickForm() { + CRM_Utils_System::setTitle(ts('Settings - CiviCase')); + parent::buildQuickForm(); + } + +} diff --git a/CRM/Admin/Form/Setting/Component.php b/CRM/Admin/Form/Setting/Component.php index 4a45be0e23bc..60d024b2b716 100644 --- a/CRM/Admin/Form/Setting/Component.php +++ b/CRM/Admin/Form/Setting/Component.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + ]; + /** * Build the form object. */ public function buildQuickForm() { - CRM_Utils_System::setTitle(ts('Settings - Enable Components')); - $components = $this->_getComponentSelectValues(); - $include = &$this->addElement('advmultiselect', 'enableComponents', - ts('Components') . ' ', $components, - array( - 'size' => 5, - 'style' => 'width:150px', - 'class' => 'advmultiselect', - ) - ); - - $include->setButtonAttributes('add', array('value' => ts('Enable >>'))); - $include->setButtonAttributes('remove', array('value' => ts('<< Disable'))); - - $this->addFormRule(array('CRM_Admin_Form_Setting_Component', 'formRule'), $this); - + $this->addFormRule(['CRM_Admin_Form_Setting_Component', 'formRule'], $this); parent::buildQuickForm(); } @@ -74,100 +63,22 @@ public function buildQuickForm() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $options) { - $errors = array(); + $errors = []; - if (array_key_exists('enableComponents', $fields) && is_array($fields['enableComponents'])) { - if (in_array('CiviPledge', $fields['enableComponents']) && - !in_array('CiviContribute', $fields['enableComponents']) + if (array_key_exists('enable_components', $fields) && is_array($fields['enable_components'])) { + if (!empty($fields['enable_components']['CiviPledge']) && + empty($fields['enable_components']['CiviContribute']) ) { - $errors['enableComponents'] = ts('You need to enable CiviContribute before enabling CiviPledge.'); + $errors['enable_components'] = ts('You need to enable CiviContribute before enabling CiviPledge.'); } - if (in_array('CiviCase', $fields['enableComponents']) && + if (!empty($fields['enable_components']['CiviCase']) && !CRM_Core_DAO::checkTriggerViewPermission(TRUE, FALSE) ) { - $errors['enableComponents'] = ts('CiviCase requires CREATE VIEW and DROP VIEW permissions for the database.'); + $errors['enable_components'] = ts('CiviCase requires CREATE VIEW and DROP VIEW permissions for the database.'); } } return $errors; } - /** - * @return array - */ - private function _getComponentSelectValues() { - $ret = array(); - $this->_components = CRM_Core_Component::getComponents(); - foreach ($this->_components as $name => $object) { - $ret[$name] = $object->info['translatedName']; - } - - return $ret; - } - - public function postProcess() { - $params = $this->controller->exportValues($this->_name); - - parent::commonProcess($params); - - // reset navigation when components are enabled / disabled - CRM_Core_BAO_Navigation::resetNavigation(); - } - - /** - * Load case sample data. - * - * @param string $fileName - * @param bool $lineMode - */ - public static function loadCaseSampleData($fileName, $lineMode = FALSE) { - $dao = new CRM_Core_DAO(); - $db = $dao->getDatabaseConnection(); - - $domain = new CRM_Core_DAO_Domain(); - $domain->find(TRUE); - $multiLingual = (bool) $domain->locales; - $smarty = CRM_Core_Smarty::singleton(); - $smarty->assign('multilingual', $multiLingual); - $smarty->assign('locales', explode(CRM_Core_DAO::VALUE_SEPARATOR, $domain->locales)); - - if (!$lineMode) { - - $string = $smarty->fetch($fileName); - // change \r\n to fix windows issues - $string = str_replace("\r\n", "\n", $string); - - //get rid of comments starting with # and -- - - $string = preg_replace("/^#[^\n]*$/m", "\n", $string); - $string = preg_replace("/^(--[^-]).*/m", "\n", $string); - - $queries = preg_split('/;$/m', $string); - foreach ($queries as $query) { - $query = trim($query); - if (!empty($query)) { - $res = &$db->query($query); - if (PEAR::isError($res)) { - die("Cannot execute $query: " . $res->getMessage()); - } - } - } - } - else { - $fd = fopen($fileName, "r"); - while ($string = fgets($fd)) { - $string = preg_replace("/^#[^\n]*$/m", "\n", $string); - $string = preg_replace("/^(--[^-]).*/m", "\n", $string); - - $string = trim($string); - if (!empty($string)) { - $res = &$db->query($string); - if (PEAR::isError($res)) { - die("Cannot execute $string: " . $res->getMessage()); - } - } - } - } - } - } diff --git a/CRM/Admin/Form/Setting/Date.php b/CRM/Admin/Form/Setting/Date.php index ae510be0965d..cd8d5976e60b 100644 --- a/CRM/Admin/Form/Setting/Date.php +++ b/CRM/Admin/Form/Setting/Date.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'dateformatFull' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'dateformatPartial' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, @@ -48,7 +48,7 @@ class CRM_Admin_Form_Setting_Date extends CRM_Admin_Form_Setting { 'dateInputFormat' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'timeInputFormat' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'fiscalYearStart' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, - ); + ]; /** * Build the form object. diff --git a/CRM/Admin/Form/Setting/Debugging.php b/CRM/Admin/Form/Setting/Debugging.php index 528c120f74d5..133ed57a9dc6 100644 --- a/CRM/Admin/Form/Setting/Debugging.php +++ b/CRM/Admin/Form/Setting/Debugging.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, 'backtrace' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, 'fatalErrorHandler' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, - ); + 'assetCache' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, + 'environment' => CRM_Core_BAO_Setting::DEVELOPER_PREFERENCES_NAME, + ]; /** * Build the form object. @@ -52,6 +54,11 @@ public function buildQuickForm() { } parent::buildQuickForm(); + if (Civi::settings()->getMandatory('environment') !== NULL) { + $element = $this->getElement('environment'); + $element->freeze(); + CRM_Core_Session::setStatus(ts('The environment settings have been disabled because it has been overridden in the settings file.'), ts('Environment settings'), 'info'); + } } } diff --git a/CRM/Admin/Form/Setting/Localization.php b/CRM/Admin/Form/Setting/Localization.php index 8e2b2718ab23..a3cec9ae90c5 100644 --- a/CRM/Admin/Form/Setting/Localization.php +++ b/CRM/Admin/Form/Setting/Localization.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'countryLimit' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'customTranslateFunction' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, @@ -52,7 +52,15 @@ class CRM_Admin_Form_Setting_Localization extends CRM_Admin_Form_Setting { 'moneyformat' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'moneyvalueformat' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, 'provinceLimit' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, - ); + 'uiLanguages' => CRM_Core_BAO_Setting::LOCALIZATION_PREFERENCES_NAME, + ]; + + public function preProcess() { + if (!CRM_Core_I18n::isMultiLingual()) { + CRM_Core_Resources::singleton() + ->addScriptFile('civicrm', 'templates/CRM/Admin/Form/Setting/Localization.js', 1, 'html-header'); + } + } /** * Build the form object. @@ -65,20 +73,17 @@ public function buildQuickForm() { $warningTitle = json_encode(ts("Warning")); $defaultLocaleOptions = CRM_Admin_Form_Setting_Localization::getDefaultLocaleOptions(); - $domain = new CRM_Core_DAO_Domain(); - $domain->find(TRUE); - - if ($domain->locales) { + if (CRM_Core_I18n::isMultiLingual()) { // add language limiter and language adder $this->addCheckBox('languageLimit', ts('Available Languages'), array_flip($defaultLocaleOptions), NULL, NULL, NULL, NULL, '   '); - $this->addElement('select', 'addLanguage', ts('Add Language'), array_merge(array('' => ts('- select -')), array_diff(CRM_Core_I18n::languages(), $defaultLocaleOptions))); + $this->addElement('select', 'addLanguage', ts('Add Language'), array_merge(['' => ts('- select -')], array_diff(CRM_Core_I18n::languages(), $defaultLocaleOptions))); // add the ability to return to single language $warning = ts('This will make your CiviCRM installation a single-language one again. THIS WILL DELETE ALL DATA RELATED TO LANGUAGES OTHER THAN THE DEFAULT ONE SELECTED ABOVE (and only that language will be preserved).'); $this->assign('warning', $warning); $warning = json_encode($warning); $this->addElement('checkbox', 'makeSinglelingual', ts('Return to Single Language'), - NULL, array('onChange' => "if (this.checked) CRM.alert($warning, $warningTitle)") + NULL, ['onChange' => "if (this.checked) CRM.alert($warning, $warningTitle)"] ); } else { @@ -91,7 +96,7 @@ public function buildQuickForm() { !\Civi::settings()->get('logging') ) { $this->addElement('checkbox', 'makeMultilingual', ts('Enable Multiple Languages'), - NULL, array('onChange' => "if (this.checked) CRM.alert($warning, $warningTitle)") + NULL, ['onChange' => "if (this.checked) CRM.alert($warning, $warningTitle)"] ); } } @@ -100,17 +105,17 @@ public function buildQuickForm() { $includeCurrency = &$this->addElement('advmultiselect', 'currencyLimit', ts('Available Currencies') . ' ', self::getCurrencySymbols(), - array( + [ 'size' => 5, 'style' => 'width:150px', 'class' => 'advmultiselect', - ) + ] ); - $includeCurrency->setButtonAttributes('add', array('value' => ts('Add >>'))); - $includeCurrency->setButtonAttributes('remove', array('value' => ts('<< Remove'))); + $includeCurrency->setButtonAttributes('add', ['value' => ts('Add >>')]); + $includeCurrency->setButtonAttributes('remove', ['value' => ts('<< Remove')]); - $this->addFormRule(array('CRM_Admin_Form_Setting_Localization', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Setting_Localization', 'formRule']); parent::buildQuickForm(); } @@ -121,7 +126,7 @@ public function buildQuickForm() { * @return array|bool */ public static function formRule($fields) { - $errors = array(); + $errors = []; if (CRM_Utils_Array::value('monetaryThousandSeparator', $fields) == CRM_Utils_Array::value('monetaryDecimalPoint', $fields) ) { @@ -132,10 +137,6 @@ public static function formRule($fields) { $errors['monetaryThousandSeparator'] = ts('Thousands Separator can not be empty. You can use a space character instead.'); } - if (strlen($fields['monetaryThousandSeparator']) > 1) { - $errors['monetaryThousandSeparator'] = ts('Thousands Separator can not have more than 1 character.'); - } - if (strlen($fields['monetaryDecimalPoint']) > 1) { $errors['monetaryDecimalPoint'] = ts('Decimal Delimiter can not have more than 1 character.'); } @@ -183,48 +184,26 @@ public function postProcess() { //cache contact fields retaining localized titles //though we changed localization, so reseting cache. - CRM_Core_BAO_Cache::deleteGroup('contact fields'); + Civi::cache('fields')->flush(); //CRM-8559, cache navigation do not respect locale if it is changed, so reseting cache. - CRM_Core_BAO_Cache::deleteGroup('navigation'); + Civi::cache('navigation')->flush(); + // reset ACL and System caches + CRM_Core_BAO_Cache::resetCaches(); // we do this only to initialize monetary decimal point and thousand separator $config = CRM_Core_Config::singleton(); - // save enabled currencies and defaul currency in option group 'currencies_enabled' + // save enabled currencies and default currency in option group 'currencies_enabled' // CRM-1496 if (empty($values['currencyLimit'])) { - $values['currencyLimit'] = array($values['defaultCurrency']); + $values['currencyLimit'] = [$values['defaultCurrency']]; } - elseif (!in_array($values['defaultCurrency'], - $values['currencyLimit'] - ) - ) { + elseif (!in_array($values['defaultCurrency'], $values['currencyLimit'])) { $values['currencyLimit'][] = $values['defaultCurrency']; } - // sort so that when we display drop down, weights have right value - sort($values['currencyLimit']); - - // get labels for all the currencies - $options = array(); - - $currencySymbols = self::getCurrencySymbols(); - for ($i = 0; $i < count($values['currencyLimit']); $i++) { - $options[] = array( - 'label' => $currencySymbols[$values['currencyLimit'][$i]], - 'value' => $values['currencyLimit'][$i], - 'weight' => $i + 1, - 'is_active' => 1, - 'is_default' => $values['currencyLimit'][$i] == $values['defaultCurrency'], - ); - } - - $dontCare = NULL; - CRM_Core_OptionGroup::createAssoc('currencies_enabled', - $options, - $dontCare - ); + self::updateEnabledCurrencies($values['currencyLimit'], $values['defaultCurrency']); // unset currencyLimit so we dont store there unset($values['currencyLimit']); @@ -241,7 +220,7 @@ public function postProcess() { } // add a new db locale if the requested language is not yet supported by the db - if (!CRM_Utils_Array::value('makeSinglelingual', $values) and CRM_Utils_Array::value('addLanguage', $values)) { + if (empty($values['makeSinglelingual']) && !empty($values['addLanguage'])) { $domain = new CRM_Core_DAO_Domain(); $domain->find(TRUE); if (!substr_count($domain->locales, $values['addLanguage'])) { @@ -250,6 +229,11 @@ public function postProcess() { $values['languageLimit'][$values['addLanguage']] = 1; } + // current language should be in the ui list + if (!in_array($values['lcMessages'], $values['uiLanguages'])) { + $values['uiLanguages'][] = $values['lcMessages']; + } + // if we manipulated the language list, return to the localization admin screen $return = (bool) (CRM_Utils_Array::value('makeMultilingual', $values) or CRM_Utils_Array::value('addLanguage', $values)); @@ -269,14 +253,44 @@ public function postProcess() { } } + /** + * Replace available currencies by the ones provided + * + * @param $currencies array of currencies ['USD', 'CAD'] + * @param $default default currency + */ + public static function updateEnabledCurrencies($currencies, $default) { + + // sort so that when we display drop down, weights have right value + sort($currencies); + + // get labels for all the currencies + $options = []; + + $currencySymbols = CRM_Admin_Form_Setting_Localization::getCurrencySymbols(); + for ($i = 0; $i < count($currencies); $i++) { + $options[] = [ + 'label' => $currencySymbols[$currencies[$i]], + 'value' => $currencies[$i], + 'weight' => $i + 1, + 'is_active' => 1, + 'is_default' => $currencies[$i] == $default, + ]; + } + + $dontCare = NULL; + CRM_Core_OptionGroup::createAssoc('currencies_enabled', $options, $dontCare); + + } + /** * @return array */ public static function getAvailableCountries() { $i18n = CRM_Core_I18n::singleton(); - $country = array(); + $country = []; CRM_Core_PseudoConstant::populate($country, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active'); - $i18n->localizeArray($country, array('context' => 'country')); + $i18n->localizeArray($country, ['context' => 'country']); asort($country); return $country; } @@ -292,7 +306,7 @@ public static function getDefaultLocaleOptions() { $locales = CRM_Core_I18n::languages(); if ($domain->locales) { // for multi-lingual sites, populate default language drop-down with available languages - $defaultLocaleOptions = array(); + $defaultLocaleOptions = []; foreach ($locales as $loc => $lang) { if (substr_count($domain->locales, $loc)) { $defaultLocaleOptions[$loc] = $lang; @@ -312,11 +326,11 @@ public static function getDefaultLocaleOptions() { * Array('USD' => 'USD ($)'). */ public static function getCurrencySymbols() { - $symbols = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', array( + $symbols = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', [ 'labelColumn' => 'symbol', 'orderColumn' => TRUE, - )); - $_currencySymbols = array(); + ]); + $_currencySymbols = []; foreach ($symbols as $key => $value) { $_currencySymbols[$key] = "$key"; if ($value) { @@ -351,15 +365,35 @@ public static function onChangeLcMessages($oldLocale, $newLocale, $metadata, $do } } + public static function onChangeDefaultCurrency($oldCurrency, $newCurrency, $metadata) { + if ($oldCurrency == $newCurrency) { + return; + } + + // ensure that default currency is always in the list of enabled currencies + $currencies = array_keys(CRM_Core_OptionGroup::values('currencies_enabled')); + if (!in_array($newCurrency, $currencies)) { + if (empty($currencies)) { + $currencies = [$values['defaultCurrency']]; + } + else { + $currencies[] = $newCurrency; + } + + CRM_Admin_Form_Setting_Localization::updateEnabledCurrencies($currencies, $newCurrency); + } + + } + /** * @return array */ public static function getDefaultLanguageOptions() { - return array( + return [ '*default*' => ts('Use default site language'), 'undefined' => ts('Leave undefined'), 'current_site_language' => ts('Use language in use at the time'), - ); + ]; } } diff --git a/CRM/Admin/Form/Setting/Mail.php b/CRM/Admin/Form/Setting/Mail.php index 960ce2abae1e..bd75e95a8381 100644 --- a/CRM/Admin/Form/Setting/Mail.php +++ b/CRM/Admin/Form/Setting/Mail.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + protected $_settings = [ 'mailerBatchLimit' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + 'mailThrottleTime' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'mailerJobSize' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'mailerJobsMax' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, - 'mailThrottleTime' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, 'verpSeparator' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, - ); + 'replyTo' => CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME, + ]; /** * Build the form object. */ public function buildQuickForm() { CRM_Utils_System::setTitle(ts('Settings - CiviMail')); - $check = TRUE; - - // redirect to Administer Section After hitting either Save or Cancel button. - $session = CRM_Core_Session::singleton(); - $session->pushUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); - - $this->addFormRule(array('CRM_Admin_Form_Setting_Mail', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Setting_Mail', 'formRule']); - parent::buildQuickForm($check); + parent::buildQuickForm(); } /** @@ -67,7 +61,7 @@ public function buildQuickForm() { * @return array|bool */ public static function formRule($fields) { - $errors = array(); + $errors = []; if (CRM_Utils_Array::value('mailerJobSize', $fields) > 0) { if (CRM_Utils_Array::value('mailerJobSize', $fields) < 1000) { diff --git a/CRM/Admin/Form/Setting/Mapping.php b/CRM/Admin/Form/Setting/Mapping.php index 9e6ac247f076..fa741e6d8514 100644 --- a/CRM/Admin/Form/Setting/Mapping.php +++ b/CRM/Admin/Form/Setting/Mapping.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, 'mapProvider' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, 'geoAPIKey' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, 'geoProvider' => CRM_Core_BAO_Setting::MAP_PREFERENCES_NAME, - ); + ]; /** * Build the form object. @@ -61,16 +61,12 @@ public function buildQuickForm() { * true if no errors, else array of errors */ public static function formRule($fields) { - $errors = array(); + $errors = []; if (!CRM_Utils_System::checkPHPVersion(5, FALSE)) { $errors['_qf_default'] = ts('Mapping features require PHP version 5 or greater'); } - if (!$fields['mapAPIKey'] && ($fields['mapProvider'] != '' && $fields['mapProvider'] == 'Yahoo')) { - $errors['mapAPIKey'] = "Map Provider key is a required field."; - } - if ($fields['mapProvider'] == 'OpenStreetMaps' && $fields['geoProvider'] == '') { $errors['geoProvider'] = "Please select a Geocoding Provider - Open Street Maps does not provide geocoding."; } @@ -84,7 +80,7 @@ public static function formRule($fields) { * All local rules are added near the element */ public function addRules() { - $this->addFormRule(array('CRM_Admin_Form_Setting_Mapping', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Setting_Mapping', 'formRule']); } } diff --git a/CRM/Admin/Form/Setting/Miscellaneous.php b/CRM/Admin/Form/Setting/Miscellaneous.php index 6f9b6457e465..d1277923a82f 100644 --- a/CRM/Admin/Form/Setting/Miscellaneous.php +++ b/CRM/Admin/Form/Setting/Miscellaneous.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'max_attachments_backend' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_undelete' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'empoweredBy' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'logging' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, @@ -49,12 +50,15 @@ class CRM_Admin_Form_Setting_Miscellaneous extends CRM_Admin_Form_Setting { 'recaptchaOptions' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'recaptchaPublicKey' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'recaptchaPrivateKey' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'forceRecaptcha' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'wkhtmltopdfPath' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'recentItemsMaxCount' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'recentItemsProviders' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'dedupe_default_limit' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'remote_profile_submissions' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, - ); + 'allow_alert_autodismissal' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'prevNextBackend' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + ]; public $_uploadMaxSize; @@ -67,15 +71,17 @@ public function preProcess() { CRM_Utils_Number::formatUnitSize(ini_get('post_max_size'), TRUE); // This is a temp hack for the fact we really don't need to hard-code each setting in the tpl but // we haven't worked through NOT doing that. These settings have been un-hardcoded. - $this->assign('pure_config_settings', array( + $this->assign('pure_config_settings', [ 'empoweredBy', 'max_attachments', + 'max_attachments_backend', 'maxFileSize', 'secondDegRelPermissions', 'recentItemsMaxCount', 'recentItemsProviders', 'dedupe_default_limit', - )); + 'prevNextBackend', + ]); } /** @@ -86,7 +92,7 @@ public function buildQuickForm() { $this->assign('validTriggerPermission', CRM_Core_DAO::checkTriggerViewPermission(FALSE)); - $this->addFormRule(array('CRM_Admin_Form_Setting_Miscellaneous', 'formRule'), $this); + $this->addFormRule(['CRM_Admin_Form_Setting_Miscellaneous', 'formRule'], $this); parent::buildQuickForm(); $this->addRule('checksum_timeout', ts('Value should be a positive number'), 'positiveInteger'); @@ -106,7 +112,7 @@ public function buildQuickForm() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $options) { - $errors = array(); + $errors = []; // validate max file size if ($fields['maxFileSize'] > $options->_uploadMaxSize) { @@ -115,7 +121,7 @@ public static function formRule($fields, $files, $options) { // validate recent items stack size if ($fields['recentItemsMaxCount'] && ($fields['recentItemsMaxCount'] < 1 || $fields['recentItemsMaxCount'] > CRM_Utils_Recent::MAX_ITEMS)) { - $errors['recentItemsMaxCount'] = ts("Illegal stack size. Use values between 1 and %1.", array(1 => CRM_Utils_Recent::MAX_ITEMS)); + $errors['recentItemsMaxCount'] = ts("Illegal stack size. Use values between 1 and %1.", [1 => CRM_Utils_Recent::MAX_ITEMS]); } if (!empty($fields['wkhtmltopdfPath'])) { diff --git a/CRM/Admin/Form/Setting/Path.php b/CRM/Admin/Form/Setting/Path.php index edb8e959bf2f..b83c52706748 100644 --- a/CRM/Admin/Form/Setting/Path.php +++ b/CRM/Admin/Form/Setting/Path.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'imageUploadDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'customFileUploadDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'customTemplateDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'customPHPPathDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, 'extensionsDir' => CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, - ); + ]; /** * Build the form object. @@ -52,19 +52,19 @@ public function buildQuickForm() { CRM_Utils_System::setTitle(ts('Settings - Upload Directories')); parent::buildQuickForm(); - $directories = array( + $directories = [ 'uploadDir' => ts('Temporary Files'), 'imageUploadDir' => ts('Images'), 'customFileUploadDir' => ts('Custom Files'), 'customTemplateDir' => ts('Custom Templates'), 'customPHPPathDir' => ts('Custom PHP Path Directory'), 'extensionsDir' => ts('CiviCRM Extensions Directory'), - ); + ]; foreach ($directories as $name => $title) { //$this->add('text', $name, $title); $this->addRule($name, ts("'%1' directory does not exist", - array(1 => $title) + [1 => $title] ), 'settingPath' ); diff --git a/CRM/Admin/Form/Setting/Search.php b/CRM/Admin/Form/Setting/Search.php index 5d4a3b2245c5..effb40d68dc0 100644 --- a/CRM/Admin/Form/Setting/Search.php +++ b/CRM/Admin/Form/Setting/Search.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - 'contact_autocomplete_options' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - 'search_autocomplete_count' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - 'enable_innodb_fts' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + protected $_settings = [ 'includeWildCardInName' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, 'includeEmailInName' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + 'searchPrimaryDetailsOnly' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, 'includeNickNameInName' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, 'includeAlphabeticalPager' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, 'includeOrderByClause' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - 'smartGroupCacheTimeout' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, 'defaultSearchProfileID' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - 'searchPrimaryDetailsOnly' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, - ); + 'smartGroupCacheTimeout' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + 'quicksearch_options' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + 'contact_autocomplete_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'contact_reference_options' => CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'search_autocomplete_count' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + 'enable_innodb_fts' => CRM_Core_BAO_Setting::SEARCH_PREFERENCES_NAME, + ]; /** * Build the form object. @@ -60,49 +61,55 @@ public function buildQuickForm() { parent::buildQuickForm(); - // @todo remove the following adds in favour of setting via the settings array (above). - - // Autocomplete for Contact Search (quick search etc.) + // Option 1 can't be unchecked. @see self::enableOptionOne $element = $this->getElement('contact_autocomplete_options'); - $element->_elements[0]->_flagFrozen = TRUE; + $element->_elements[0]->setAttribute('disabled', 'disabled'); - // Autocomplete for Contact Reference (custom fields) + // Option 1 can't be unchecked. @see self::enableOptionOne $element = $this->getElement('contact_reference_options'); - $element->_elements[0]->_flagFrozen = TRUE; + $element->_elements[0]->setAttribute('disabled', 'disabled'); } /** * @return array */ public static function getContactAutocompleteOptions() { - return array( - ts('Contact Name') => 1, - ) + array_flip(CRM_Core_OptionGroup::values('contact_autocomplete_options', - FALSE, FALSE, TRUE - )); + return [1 => ts('Contact Name')] + CRM_Core_OptionGroup::values('contact_autocomplete_options', FALSE, FALSE, TRUE); } /** * @return array */ public static function getAvailableProfiles() { - return array('' => ts('- none -')) + CRM_Core_BAO_UFGroup::getProfiles(array( + return ['' => ts('- none -')] + CRM_Core_BAO_UFGroup::getProfiles([ 'Contact', 'Individual', 'Organization', 'Household', - )); + ]); } /** * @return array */ public static function getContactReferenceOptions() { - return array( - ts('Contact Name') => 1, - ) + array_flip(CRM_Core_OptionGroup::values('contact_reference_options', - FALSE, FALSE, TRUE - )); + return [1 => ts('Contact Name')] + CRM_Core_OptionGroup::values('contact_reference_options', FALSE, FALSE, TRUE); + } + + /** + * Presave callback for contact_reference_options and contact_autocomplete_options. + * + * Ensures "1" is always contained in the array. + * + * @param $value + * @return bool + */ + public static function enableOptionOne(&$value) { + $values = (array) CRM_Utils_Array::explodePadded($value); + if (!in_array(1, $values)) { + $value = CRM_Utils_Array::implodePadded(array_merge([1], $values)); + } + return TRUE; } } diff --git a/CRM/Admin/Form/Setting/Smtp.php b/CRM/Admin/Form/Setting/Smtp.php index 0be1226d70a1..706bf874e991 100644 --- a/CRM/Admin/Form/Setting/Smtp.php +++ b/CRM/Admin/Form/Setting/Smtp.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::DIRECTORY_PREFERENCES_NAME, + ]; /** * Build the form object. */ public function buildQuickForm() { - $outBoundOption = array( + $outBoundOption = [ CRM_Mailing_Config::OUTBOUND_OPTION_MAIL => ts('mail()'), CRM_Mailing_Config::OUTBOUND_OPTION_SMTP => ts('SMTP'), CRM_Mailing_Config::OUTBOUND_OPTION_SENDMAIL => ts('Sendmail'), CRM_Mailing_Config::OUTBOUND_OPTION_DISABLED => ts('Disable Outbound Email'), CRM_Mailing_Config::OUTBOUND_OPTION_REDIRECT_TO_DB => ts('Redirect to Database'), - ); + ]; $this->addRadio('outBound_option', ts('Select Mailer'), $outBoundOption); + $props = array(); + $settings = Civi::settings()->getMandatory('mailing_backend') ?? []; + //Load input as readonly whose values are overridden in civicrm.settings.php. + foreach ($settings as $setting => $value) { + if (isset($value)) { + $props[$setting]['readonly'] = TRUE; + $setStatus = TRUE; + } + } + CRM_Utils_System::setTitle(ts('Settings - Outbound Mail')); $this->add('text', 'sendmail_path', ts('Sendmail Path')); $this->add('text', 'sendmail_args', ts('Sendmail Argument')); - $this->add('text', 'smtpServer', ts('SMTP Server')); - $this->add('text', 'smtpPort', ts('SMTP Port')); - $this->addYesNo('smtpAuth', ts('Authentication?')); - $this->addElement('text', 'smtpUsername', ts('SMTP Username')); - $this->addElement('password', 'smtpPassword', ts('SMTP Password')); + $this->add('text', 'smtpServer', ts('SMTP Server'), CRM_Utils_Array::value('smtpServer', $props)); + $this->add('text', 'smtpPort', ts('SMTP Port'), CRM_Utils_Array::value('smtpPort', $props)); + $this->addYesNo('smtpAuth', ts('Authentication?'), CRM_Utils_Array::value('smtpAuth', $props)); + $this->addElement('text', 'smtpUsername', ts('SMTP Username'), CRM_Utils_Array::value('smtpUsername', $props)); + $this->addElement('password', 'smtpPassword', ts('SMTP Password'), CRM_Utils_Array::value('smtpPassword', $props)); $this->_testButtonName = $this->getButtonName('refresh', 'test'); - $this->addFormRule(array('CRM_Admin_Form_Setting_Smtp', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Setting_Smtp', 'formRule']); parent::buildQuickForm(); $buttons = $this->getElement('buttons')->getElements(); - $buttons[] = $this->createElement('submit', $this->_testButtonName, ts('Save & Send Test Email'), array('crm-icon' => 'fa-envelope-o')); + $buttons[] = $this->createElement('submit', $this->_testButtonName, ts('Save & Send Test Email'), ['crm-icon' => 'fa-envelope-o']); $this->getElement('buttons')->setElements($buttons); + + if (!empty($setStatus)) { + CRM_Core_Session::setStatus("Some fields are loaded as 'readonly' as they have been set (overridden) in civicrm.settings.php.", '', 'info', array('expires' => 0)); + } } /** * Process the form submission. + * + * @throws \Exception */ public function postProcess() { // flush caches so we reload details for future requests @@ -79,6 +98,9 @@ public function postProcess() { $formValues = $this->controller->exportValues($this->_name); + Civi::settings()->set('allow_mail_from_logged_in_contact', (!empty($formValues['allow_mail_from_logged_in_contact']))); + unset($formValues['allow_mail_from_logged_in_contact']); + $buttonName = $this->controller->getButtonName(); // check if test button if ($buttonName == $this->_testButtonName) { @@ -91,16 +113,15 @@ public function postProcess() { else { $session = CRM_Core_Session::singleton(); $userID = $session->get('userID'); - list($toDisplayName, $toEmail, $toDoNotEmail) = CRM_Contact_BAO_Contact::getContactDetails($userID); + list($toDisplayName, $toEmail) = CRM_Contact_BAO_Contact::getContactDetails($userID); //get the default domain email address.CRM-4250 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain::getNameAndEmail(); if (!$domainEmailAddress || $domainEmailAddress == 'info@EXAMPLE.ORG') { - $fixUrl = CRM_Utils_System::url("civicrm/admin/domain", 'action=update&reset=1'); - CRM_Core_Error::fatal(ts('The site administrator needs to enter a valid \'FROM Email Address\' in Administer CiviCRM » Communications » FROM Email Addresses. The email address used may need to be a valid mail account with your email service provider.', array(1 => $fixUrl))); + $fixUrl = CRM_Utils_System::url("civicrm/admin/options/from_email_address", 'action=update&reset=1'); + CRM_Core_Error::statusBounce(ts('The site administrator needs to enter a valid \'FROM Email Address\' in Administer CiviCRM » System Settings » Option Groups » From Email Address. The email address used may need to be a valid mail account with your email service provider.', [1 => $fixUrl])); } - if (!$toEmail) { CRM_Core_Error::statusBounce(ts('Cannot send a test email because your user record does not have a valid email address.')); } @@ -111,12 +132,11 @@ public function postProcess() { $to = '"' . $toDisplayName . '"' . "<$toEmail>"; $from = '"' . $domainEmailName . '" <' . $domainEmailAddress . '>'; - $testMailStatusMsg = ts('Sending test email. FROM: %1 TO: %2.
', array( - 1 => $domainEmailAddress, - 2 => $toEmail, - )); + $testMailStatusMsg = ts('Sending test email') . ':
' + . ts('From: %1', [1 => $domainEmailAddress]) . '
' + . ts('To: %1', [1 => $toEmail]) . '
'; - $params = array(); + $params = []; if ($formValues['outBound_option'] == CRM_Mailing_Config::OUTBOUND_OPTION_SMTP) { $subject = "Test for SMTP settings"; $message = "SMTP settings are correct."; @@ -155,23 +175,29 @@ public function postProcess() { $mailerName = 'mail'; } - $headers = array( + $headers = [ 'From' => $from, 'To' => $to, 'Subject' => $subject, - ); + ]; $mailer = Mail::factory($mailerName, $params); $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); $result = $mailer->send($toEmail, $headers, $message); unset($errorScope); - if (!is_a($result, 'PEAR_Error')) { - CRM_Core_Session::setStatus($testMailStatusMsg . ts('Your %1 settings are correct. A test email has been sent to your email address.', array(1 => strtoupper($mailerName))), ts("Mail Sent"), "success"); + if (defined('CIVICRM_MAIL_LOG') && defined('CIVICRM_MAIL_LOG_AND_SEND')) { + $testMailStatusMsg .= '
' . ts('You have defined CIVICRM_MAIL_LOG_AND_SEND - mail will be logged.') . '

'; + } + if (defined('CIVICRM_MAIL_LOG') && !defined('CIVICRM_MAIL_LOG_AND_SEND')) { + CRM_Core_Session::setStatus($testMailStatusMsg . ts('You have defined CIVICRM_MAIL_LOG - no mail will be sent. Your %1 settings have not been tested.', [1 => strtoupper($mailerName)]), ts("Mail not sent"), "warning"); + } + elseif (!is_a($result, 'PEAR_Error')) { + CRM_Core_Session::setStatus($testMailStatusMsg . ts('Your %1 settings are correct. A test email has been sent to your email address.', [1 => strtoupper($mailerName)]), ts("Mail Sent"), "success"); } else { $message = CRM_Utils_Mail::errorMessage($mailer, $result); - CRM_Core_Session::setStatus($testMailStatusMsg . ts('Oops. Your %1 settings are incorrect. No test mail has been sent.', array(1 => strtoupper($mailerName))) . $message, ts("Mail Not Sent"), "error"); + CRM_Core_Session::setStatus($testMailStatusMsg . ts('Oops. Your %1 settings are incorrect. No test mail has been sent.', [1 => strtoupper($mailerName)]) . $message, ts("Mail Not Sent"), "error"); } } } @@ -196,7 +222,7 @@ public function postProcess() { * @param array $fields * Posted values of the form. * - * @return array + * @return array|bool * list of errors to be posted back to the form */ public static function formRule($fields) { @@ -233,7 +259,7 @@ public static function formRule($fields) { */ public function setDefaultValues() { if (!$this->_defaults) { - $this->_defaults = array(); + $this->_defaults = []; $mailingBackend = Civi::settings()->get('mailing_backend'); if (!empty($mailingBackend)) { @@ -256,6 +282,7 @@ public function setDefaultValues() { } } } + $this->_defaults['allow_mail_from_logged_in_contact'] = Civi::settings()->get('allow_mail_from_logged_in_contact'); return $this->_defaults; } diff --git a/CRM/Admin/Form/Setting/UF.php b/CRM/Admin/Form/Setting/UF.php index ab290514d38b..efb8e9a44fbb 100644 --- a/CRM/Admin/Form/Setting/UF.php +++ b/CRM/Admin/Form/Setting/UF.php @@ -1,9 +1,9 @@ $this->_uf)) + ts('Settings - %1 Integration', [1 => $this->_uf]) ); if ($config->userSystem->is_drupal) { @@ -82,7 +82,11 @@ function_exists('module_exists') && $dsnArray = DB::parseDSN($config->dsn); $tableNames = CRM_Core_DAO::getTableNames(); $tablePrefixes = '$databases[\'default\'][\'default\'][\'prefix\']= array('; - $tablePrefixes .= "\n 'default' => '$drupal_prefix',"; // add default prefix: the drupal database prefix + if ($config->userFramework === 'Backdrop') { + $tablePrefixes = '$database_prefix = array('; + } + // add default prefix: the drupal database prefix + $tablePrefixes .= "\n 'default' => '$drupal_prefix',"; $prefix = ""; if ($config->dsn != $config->userFrameworkDSN) { $prefix = "`{$dsnArray['database']}`."; diff --git a/CRM/Admin/Form/Setting/UpdateConfigBackend.php b/CRM/Admin/Form/Setting/UpdateConfigBackend.php index e1b6a3627e74..d4cf4ae094fd 100644 --- a/CRM/Admin/Form/Setting/UpdateConfigBackend.php +++ b/CRM/Admin/Form/Setting/UpdateConfigBackend.php @@ -1,9 +1,9 @@ addElement( - 'submit', $this->getButtonName('next', 'cleanup'), 'Cleanup Caches', - array('class' => 'crm-form-submit', 'id' => 'cleanup-cache') - ); - - $this->addElement( - 'submit', $this->getButtonName('next', 'resetpaths'), 'Reset Paths', - array('class' => 'crm-form-submit', 'id' => 'resetpaths') - ); - - //parent::buildQuickForm(); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Cleanup Caches'), + 'subName' => 'cleanup', + 'icon' => 'fa-undo', + + ], + [ + 'type' => 'next', + 'name' => ts('Reset Paths'), + 'subName' => 'resetpaths', + 'icon' => 'fa-terminal', + ], + ]); } public function postProcess() { - if (!empty($_POST['_qf_UpdateConfigBackend_next_cleanup'])) { - + if (isset($_REQUEST['_qf_UpdateConfigBackend_next_cleanup'])) { $config = CRM_Core_Config::singleton(); // cleanup templates_c directory @@ -65,6 +68,7 @@ public function postProcess() { // clear all caches CRM_Core_Config::clearDBCache(); + Civi::cache('session')->clear(); CRM_Utils_System::flushCache(); parent::rebuildMenu(); @@ -73,14 +77,13 @@ public function postProcess() { CRM_Core_Session::setStatus(ts('Cache has been cleared and menu has been rebuilt successfully.'), ts("Success"), "success"); } - - if (!empty($_POST['_qf_UpdateConfigBackend_next_resetpaths'])) { + elseif (isset($_REQUEST['_qf_UpdateConfigBackend_next_resetpaths'])) { $msg = CRM_Core_BAO_ConfigSetting::doSiteMove(); CRM_Core_Session::setStatus($msg, ts("Success"), "success"); } - return CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/setting/updateConfigBackend', 'reset=1')); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/setting/updateConfigBackend', 'reset=1')); } } diff --git a/CRM/Admin/Form/Setting/Url.php b/CRM/Admin/Form/Setting/Url.php index b629a2b6d6c5..9d6a62245553 100644 --- a/CRM/Admin/Form/Setting/Url.php +++ b/CRM/Admin/Form/Setting/Url.php @@ -1,9 +1,9 @@ CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'userFrameworkResourceURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, 'imageUploadURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, 'customCSSURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, 'extensionsURL' => CRM_Core_BAO_Setting::URL_PREFERENCES_NAME, - ); + ]; /** * Build the form object. */ public function buildQuickForm() { CRM_Utils_System::setTitle(ts('Settings - Resource URLs')); - $settingFields = civicrm_api('setting', 'getfields', array( + $settingFields = civicrm_api('setting', 'getfields', [ 'version' => 3, - )); + ]); $this->addYesNo('enableSSL', ts('Force Secure URLs (SSL)')); $this->addYesNo('verifySSL', ts('Verify SSL Certs')); // FIXME: verifySSL should use $_settings instead of manually adding fields $this->assign('verifySSL_description', $settingFields['values']['verifySSL']['description']); - $this->addFormRule(array('CRM_Admin_Form_Setting_Url', 'formRule')); + $this->addFormRule(['CRM_Admin_Form_Setting_Url', 'formRule']); parent::buildQuickForm(); } @@ -78,9 +78,9 @@ public static function formRule($fields) { ) ); if (!CRM_Utils_System::checkURL($url, TRUE)) { - $errors = array( + $errors = [ 'enableSSL' => ts('You need to set up a secure server before you can use the Force Secure URLs option'), - ); + ]; return $errors; } } diff --git a/CRM/Admin/Form/SettingTrait.php b/CRM/Admin/Form/SettingTrait.php new file mode 100644 index 000000000000..ec6e15cfa71d --- /dev/null +++ b/CRM/Admin/Form/SettingTrait.php @@ -0,0 +1,382 @@ +_settings. + * + * @return array + */ + protected function getSettingsMetaData() { + if (empty($this->settingsMetadata)) { + $this->settingsMetadata = \Civi\Core\SettingsMetadata::getMetadata(['name' => array_keys($this->_settings)], NULL, TRUE); + // This array_merge re-orders to the key order of $this->_settings. + $this->settingsMetadata = array_merge($this->_settings, $this->settingsMetadata); + } + return $this->settingsMetadata; + } + + /** + * Get the settings which can be stored based on metadata. + * + * @param array $params + * @return array + */ + protected function getSettingsToSetByMetadata($params) { + $setValues = array_intersect_key($params, $this->_settings); + // Checkboxes will be unset rather than empty so we need to add them back in. + // Handle quickform hateability just once, right here right now. + $unsetValues = array_diff_key($this->_settings, $params); + foreach ($unsetValues as $key => $unsetValue) { + if ($this->getQuickFormType($this->getSettingMetadata($key)) === 'CheckBox') { + $setValues[$key] = [$key => 0]; + } + } + return $setValues; + } + + /** + * @param $params + */ + protected function filterParamsSetByMetadata(&$params) { + foreach ($this->getSettingsToSetByMetadata($params) as $setting => $settingGroup) { + //@todo array_diff this + unset($params[$setting]); + } + } + + /** + * Get the metadata for a particular field. + * + * @param $setting + * @return mixed + */ + protected function getSettingMetadata($setting) { + return $this->getSettingsMetaData()[$setting]; + } + + /** + * Get the metadata for a particular field for a particular item. + * + * e.g get 'serialize' key, if exists, for a field. + * + * @param $setting + * @param $item + * @return mixed + */ + protected function getSettingMetadataItem($setting, $item) { + return CRM_Utils_Array::value($item, $this->getSettingsMetaData()[$setting]); + } + + /** + * @return string + */ + protected function getSettingPageFilter() { + if (!isset($this->_filter)) { + // Get the last URL component without modifying the urlPath property. + $urlPath = array_values($this->urlPath); + $this->_filter = end($urlPath); + } + return $this->_filter; + } + + /** + * Returns a re-keyed copy of the settings, ordered by weight. + * + * @return array + */ + protected function getSettingsOrderedByWeight() { + $settingMetaData = $this->getSettingsMetaData(); + $filter = $this->getSettingPageFilter(); + + usort($settingMetaData, function ($a, $b) use ($filter) { + // Handle cases in which a comparison is impossible. Such will be considered ties. + if ( + // A comparison can't be made unless both setting weights are declared. + !isset($a['settings_pages'][$filter]['weight'], $b['settings_pages'][$filter]['weight']) + // A pair of settings might actually have the same weight. + || $a['settings_pages'][$filter]['weight'] === $b['settings_pages'][$filter]['weight'] + ) { + return 0; + } + + return $a['settings_pages'][$filter]['weight'] > $b['settings_pages'][$filter]['weight'] ? 1 : -1; + }); + + return $settingMetaData; + } + + /** + * Add fields in the metadata to the template. + */ + protected function addFieldsDefinedInSettingsMetadata() { + $settingMetaData = $this->getSettingsMetaData(); + $descriptions = []; + foreach ($settingMetaData as $setting => $props) { + $quickFormType = $this->getQuickFormType($props); + if (isset($quickFormType)) { + $options = CRM_Utils_Array::value('options', $props); + if ($options) { + if ($props['html_type'] === 'Select' && isset($props['is_required']) && $props['is_required'] === FALSE && !isset($options[''])) { + // If the spec specifies the field is not required add a null option. + // Why not if empty($props['is_required']) - basically this has been added to the spec & might not be set to TRUE + // when it is true. + $options = ['' => ts('None')] + $options; + } + } + if ($props['type'] === 'Boolean') { + $options = [$props['title'] => $props['name']]; + } + + //Load input as readonly whose values are overridden in civicrm.settings.php. + if (Civi::settings()->getMandatory($setting)) { + $props['html_attributes']['readonly'] = TRUE; + $this->includesReadOnlyFields = TRUE; + } + + $add = 'add' . $quickFormType; + if ($add == 'addElement') { + $this->$add( + $props['html_type'], + $setting, + $props['title'], + ($options !== NULL) ? $options : CRM_Utils_Array::value('html_attributes', $props, []), + ($options !== NULL) ? CRM_Utils_Array::value('html_attributes', $props, []) : NULL + ); + } + elseif ($add == 'addSelect') { + $this->addElement('select', $setting, $props['title'], $options, CRM_Utils_Array::value('html_attributes', $props)); + } + elseif ($add == 'addCheckBox') { + $this->addCheckBox($setting, '', $options, NULL, CRM_Utils_Array::value('html_attributes', $props), NULL, NULL, ['  ']); + } + elseif ($add == 'addCheckBoxes') { + $newOptions = array_flip($options); + $classes = 'crm-checkbox-list'; + if (!empty($props['sortable'])) { + $classes .= ' crm-sortable-list'; + $newOptions = array_flip(self::reorderSortableOptions($setting, $options)); + } + $settingMetaData[$setting]['wrapper_element'] = ['']; + $this->addCheckBox($setting, + $props['title'], + $newOptions, + NULL, NULL, NULL, NULL, + '
  • ' + ); + } + elseif ($add == 'addChainSelect') { + $this->addChainSelect($setting, [ + 'label' => $props['title'], + ]); + } + elseif ($add == 'addMonthDay') { + $this->add('date', $setting, $props['title'], CRM_Core_SelectValues::date(NULL, 'M d')); + } + elseif ($add === 'addEntityRef') { + $this->$add($setting, $props['title'], $props['entity_reference_options']); + } + elseif ($add === 'addYesNo' && ($props['type'] === 'Boolean')) { + $this->addRadio($setting, $props['title'], [1 => 'Yes', 0 => 'No'], NULL, '  '); + } + elseif ($add === 'add') { + $this->add($props['html_type'], $setting, $props['title'], $options); + } + else { + $this->$add($setting, $props['title'], $options); + } + // Migrate to using an array as easier in smart... + $description = CRM_Utils_Array::value('description', $props); + $descriptions[$setting] = $description; + $this->assign("{$setting}_description", $description); + if ($setting == 'max_attachments') { + //temp hack @todo fix to get from metadata + $this->addRule('max_attachments', ts('Value should be a positive number'), 'positiveInteger'); + } + if ($setting == 'max_attachments_backend') { + //temp hack @todo fix to get from metadata + $this->addRule('max_attachments_backend', ts('Value should be a positive number'), 'positiveInteger'); + } + if ($setting == 'maxFileSize') { + //temp hack + $this->addRule('maxFileSize', ts('Value should be a positive number'), 'positiveInteger'); + } + + } + } + // setting_description should be deprecated - see Mail.tpl for metadata based tpl. + $this->assign('setting_descriptions', $descriptions); + $this->assign('settings_fields', $settingMetaData); + $this->assign('fields', $this->getSettingsOrderedByWeight()); + } + + /** + * Get the quickform type for the given html type. + * + * @param array $spec + * + * @return string + */ + protected function getQuickFormType($spec) { + if (isset($spec['quick_form_type']) && + !($spec['quick_form_type'] === 'Element' && !empty($spec['html_type']))) { + // This is kinda transitional + return $spec['quick_form_type']; + } + + // The spec for settings has been updated for consistency - we provide deprecation notices for sites that have + // not made this change. + $htmlType = $spec['html_type']; + if ($htmlType !== strtolower($htmlType)) { + // Avoiding 'ts' for obscure strings. + CRM_Core_Error::deprecatedFunctionWarning('Settings fields html_type should be lower case - see https://docs.civicrm.org/dev/en/latest/framework/setting/ - this needs to be fixed for ' . $spec['name']); + $htmlType = strtolower($spec['html_type']); + } + $mapping = [ + 'checkboxes' => 'CheckBoxes', + 'checkbox' => 'CheckBox', + 'radio' => 'Radio', + 'select' => 'Select', + 'textarea' => 'Element', + 'text' => 'Element', + 'entity_reference' => 'EntityRef', + 'advmultiselect' => 'Element', + ]; + $mapping += array_fill_keys(CRM_Core_Form::$html5Types, ''); + return $mapping[$htmlType]; + } + + /** + * Get the defaults for all fields defined in the metadata. + * + * All others are pending conversion. + */ + protected function setDefaultsForMetadataDefinedFields() { + CRM_Core_BAO_ConfigSetting::retrieve($this->_defaults); + foreach (array_keys($this->_settings) as $setting) { + $this->_defaults[$setting] = civicrm_api3('setting', 'getvalue', ['name' => $setting]); + $spec = $this->getSettingsMetadata()[$setting]; + if (!empty($spec['serialize'])) { + $this->_defaults[$setting] = CRM_Core_DAO::unSerializeField($this->_defaults[$setting], $spec['serialize']); + } + if ($this->getQuickFormType($spec) === 'CheckBoxes') { + $this->_defaults[$setting] = array_fill_keys($this->_defaults[$setting], 1); + } + if ($this->getQuickFormType($spec) === 'CheckBox') { + $this->_defaults[$setting] = [$setting => $this->_defaults[$setting]]; + } + } + } + + /** + * Save any fields which have been defined via metadata. + * + * (Other fields are hack-handled... sadly. + * + * @param array $params + * Form input. + * + * @throws \CiviCRM_API3_Exception + */ + protected function saveMetadataDefinedSettings($params) { + $settings = $this->getSettingsToSetByMetadata($params); + foreach ($settings as $setting => $settingValue) { + $settingMetaData = $this->getSettingMetadata($setting); + if (!empty($settingMetaData['sortable'])) { + $settings[$setting] = $this->getReorderedSettingData($setting, $settingValue); + } + elseif ($this->getQuickFormType($settingMetaData) === 'CheckBoxes') { + $settings[$setting] = array_keys($settingValue); + } + elseif ($this->getQuickFormType($settingMetaData) === 'CheckBox') { + // This will be an array with one value. + $settings[$setting] = (int) reset($settings[$setting]); + } + } + civicrm_api3('setting', 'create', $settings); + } + + /** + * Display options in correct order on the form + * + * @param $setting + * @param $options + * @return array + */ + public static function reorderSortableOptions($setting, $options) { + return array_merge(array_flip(Civi::settings()->get($setting)), $options); + } + + /** + * @param string $setting + * @param array $settingValue + * + * @return array + */ + private function getReorderedSettingData($setting, $settingValue) { + // Get order from $_POST as $_POST maintains the order the sorted setting + // options were sent. You can simply assign data from $_POST directly to + // $settings[] but preference has to be given to data from Quickform. + $order = array_keys(\CRM_Utils_Request::retrieve($setting, 'String')); + $settingValueKeys = array_keys($settingValue); + return array_intersect($order, $settingValueKeys); + } + +} diff --git a/CRM/Admin/Form/WordReplacements.php b/CRM/Admin/Form/WordReplacements.php index 11c4bfbf8da5..7213107cf547 100644 --- a/CRM/Admin/Form/WordReplacements.php +++ b/CRM/Admin/Form/WordReplacements.php @@ -1,9 +1,9 @@ _defaults; } - $this->_defaults = array(); + $this->_defaults = []; $config = CRM_Core_Config::singleton(); $values = CRM_Core_BAO_WordReplacement::getLocaleCustomStrings($config->lcMessages); $i = 1; - $enableDisable = array( + $enableDisable = [ 1 => 'enabled', 0 => 'disabled', - ); + ]; - $cardMatch = array('wildcardMatch', 'exactMatch'); + $cardMatch = ['wildcardMatch', 'exactMatch']; foreach ($enableDisable as $key => $val) { foreach ($cardMatch as $kc => $vc) { @@ -112,9 +112,9 @@ public function buildQuickForm() { } $soInstances = range(1, $this->_numStrings, 1); - $stringOverrideInstances = array(); + $stringOverrideInstances = []; if ($this->_soInstance) { - $soInstances = array($this->_soInstance); + $soInstances = [$this->_soInstance]; } elseif (!empty($_POST['old'])) { $soInstances = $stringOverrideInstances = array_keys($_POST['old']); @@ -127,8 +127,8 @@ public function buildQuickForm() { } foreach ($soInstances as $instance) { $this->addElement('checkbox', "enabled[$instance]"); - $this->add('textarea', "old[$instance]", NULL, array('rows' => 1, 'cols' => 40)); - $this->add('textarea', "new[$instance]", NULL, array('rows' => 1, 'cols' => 40)); + $this->add('text', "old[$instance]", NULL); + $this->add('text', "new[$instance]", NULL); $this->addElement('checkbox', "cb[$instance]"); } $this->assign('numStrings', $this->_numStrings); @@ -138,19 +138,18 @@ public function buildQuickForm() { $this->assign('stringOverrideInstances', empty($stringOverrideInstances) ? FALSE : $stringOverrideInstances); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); - $this->addFormRule(array('CRM_Admin_Form_WordReplacements', 'formRule'), $this); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + $this->addFormRule(['CRM_Admin_Form_WordReplacements', 'formRule'], $this); } /** @@ -163,7 +162,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($values) { - $errors = array(); + $errors = []; $oldValues = CRM_Utils_Array::value('old', $values); $newValues = CRM_Utils_Array::value('new', $values); @@ -195,32 +194,32 @@ public function postProcess() { $params = $this->controller->exportValues($this->_name); $this->_numStrings = count($params['old']); - $enabled['exactMatch'] = $enabled['wildcardMatch'] = $disabled['exactMatch'] = $disabled['wildcardMatch'] = array(); + $enabled['exactMatch'] = $enabled['wildcardMatch'] = $disabled['exactMatch'] = $disabled['wildcardMatch'] = []; for ($i = 1; $i <= $this->_numStrings; $i++) { if (!empty($params['new'][$i]) && !empty($params['old'][$i])) { if (isset($params['enabled']) && !empty($params['enabled'][$i])) { if (!empty($params['cb']) && !empty($params['cb'][$i])) { - $enabled['exactMatch'] += array($params['old'][$i] => $params['new'][$i]); + $enabled['exactMatch'] += [$params['old'][$i] => $params['new'][$i]]; } else { - $enabled['wildcardMatch'] += array($params['old'][$i] => $params['new'][$i]); + $enabled['wildcardMatch'] += [$params['old'][$i] => $params['new'][$i]]; } } else { if (isset($params['cb']) && is_array($params['cb']) && array_key_exists($i, $params['cb'])) { - $disabled['exactMatch'] += array($params['old'][$i] => $params['new'][$i]); + $disabled['exactMatch'] += [$params['old'][$i] => $params['new'][$i]]; } else { - $disabled['wildcardMatch'] += array($params['old'][$i] => $params['new'][$i]); + $disabled['wildcardMatch'] += [$params['old'][$i] => $params['new'][$i]]; } } } } - $overrides = array( + $overrides = [ 'enabled' => $enabled, 'disabled' => $disabled, - ); + ]; $config = CRM_Core_Config::singleton(); CRM_Core_BAO_WordReplacement::setLocaleCustomStrings($config->lcMessages, $overrides); diff --git a/CRM/Admin/Page/AJAX.php b/CRM/Admin/Page/AJAX.php index 35f5f2aabd56..109307216c7d 100644 --- a/CRM/Admin/Page/AJAX.php +++ b/CRM/Admin/Page/AJAX.php @@ -1,9 +1,9 @@ get('userID'); - if ($contactID) { - CRM_Core_Page_AJAX::setJsHeaders(); - $smarty = CRM_Core_Smarty::singleton(); - $smarty->assign('includeEmail', civicrm_api3('setting', 'getvalue', array('name' => 'includeEmailInName', 'group' => 'Search Preferences'))); - print $smarty->fetchWith('CRM/common/navigation.js.tpl', array( - 'navigation' => CRM_Core_BAO_Navigation::createNavigation($contactID), - )); + public static function navMenu() { + if (CRM_Core_Session::getLoggedInContactID()) { + + $menu = CRM_Core_BAO_Navigation::buildNavigationTree(); + CRM_Core_BAO_Navigation::buildHomeMenu($menu); + CRM_Utils_Hook::navigationMenu($menu); + CRM_Core_BAO_Navigation::fixNavigationMenu($menu); + CRM_Core_BAO_Navigation::orderByWeight($menu); + CRM_Core_BAO_Navigation::filterByPermission($menu); + self::formatMenuItems($menu); + + $output = [ + 'menu' => $menu, + 'search' => CRM_Utils_Array::makeNonAssociative(self::getSearchOptions()), + ]; + // Encourage browsers to cache for a long time - 1 year + $ttl = 60 * 60 * 24 * 364; + CRM_Utils_System::setHttpHeader('Expires', gmdate('D, d M Y H:i:s \G\M\T', time() + $ttl)); + CRM_Utils_System::setHttpHeader('Cache-Control', "max-age=$ttl, public"); + CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); + print (json_encode($output)); } CRM_Utils_System::civiExit(); } + /** + * @param array $menu + */ + public static function formatMenuItems(&$menu) { + foreach ($menu as $key => &$item) { + $props = $item['attributes']; + unset($item['attributes']); + if (!empty($props['separator'])) { + $item['separator'] = ($props['separator'] == 1 ? 'bottom' : 'top'); + } + if (!empty($props['icon'])) { + $item['icon'] = $props['icon']; + } + if (!empty($props['attr'])) { + $item['attr'] = $props['attr']; + } + if (!empty($props['url'])) { + $item['url'] = CRM_Utils_System::evalUrl(CRM_Core_BAO_Navigation::makeFullyFormedUrl($props['url'])); + } + if (!empty($props['label'])) { + $item['label'] = ts($props['label'], ['context' => 'menu']); + } + $item['name'] = !empty($props['name']) ? $props['name'] : CRM_Utils_String::munge(CRM_Utils_Array::value('label', $props)); + if (!empty($item['child'])) { + self::formatMenuItems($item['child']); + } + } + $menu = array_values($menu); + } + + public static function getSearchOptions() { + $searchOptions = Civi::settings()->get('quicksearch_options'); + $labels = CRM_Core_SelectValues::quicksearchOptions(); + $result = []; + foreach ($searchOptions as $key) { + $label = $labels[$key]; + if (strpos($key, 'custom_') === 0) { + $key = 'custom_' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', substr($key, 7), 'id', 'name'); + $label = array_slice(explode(': ', $label, 2), -1); + } + $result[$key] = $label; + } + return $result; + } + /** * Process drag/move action for menu tree. */ @@ -68,16 +123,16 @@ public static function getStatusMsg() { require_once 'api/v3/utils.php'; $recordID = CRM_Utils_Type::escape($_GET['id'], 'Integer'); $entity = CRM_Utils_Type::escape($_GET['entity'], 'String'); - $ret = array(); + $ret = []; if ($recordID && $entity && $recordBAO = _civicrm_api3_get_BAO($entity)) { switch ($recordBAO) { case 'CRM_Core_BAO_UFGroup': $method = 'getUFJoinRecord'; - $result = array($recordBAO, $method); - $ufJoin = call_user_func_array(($result), array($recordID, TRUE)); + $result = [$recordBAO, $method]; + $ufJoin = call_user_func_array(($result), [$recordID, TRUE]); if (!empty($ufJoin)) { - $ret['content'] = ts('This profile is currently used for %1.', array(1 => implode(', ', $ufJoin))) . '

    ' . ts('If you disable the profile - it will be removed from these forms and/or modules. Do you want to continue?'); + $ret['content'] = ts('This profile is currently used for %1.', [1 => implode(', ', $ufJoin)]) . '

    ' . ts('If you disable the profile - it will be removed from these forms and/or modules. Do you want to continue?'); } else { $ret['content'] = ts('Are you sure you want to disable this profile?'); @@ -91,12 +146,12 @@ public static function getStatusMsg() { if (!CRM_Utils_System::isNull($usedBy)) { $template = CRM_Core_Smarty::singleton(); $template->assign('usedBy', $usedBy); - $comps = array( + $comps = [ 'Event' => 'civicrm_event', 'Contribution' => 'civicrm_contribution_page', 'EventTemplate' => 'civicrm_event_template', - ); - $contexts = array(); + ]; + $contexts = []; foreach ($comps as $name => $table) { if (array_key_exists($table, $usedBy)) { $contexts[] = $name; @@ -106,12 +161,12 @@ public static function getStatusMsg() { $ret['illegal'] = TRUE; $table = $template->fetch('CRM/Price/Page/table.tpl'); - $ret['content'] = ts('Unable to disable the \'%1\' price set - it is currently in use by one or more active events, contribution pages or contributions.', array( - 1 => $priceSet, - )) . "
    $table"; + $ret['content'] = ts('Unable to disable the \'%1\' price set - it is currently in use by one or more active events, contribution pages or contributions.', [ + 1 => $priceSet, + ]) . "
    $table"; } else { - $ret['content'] = ts('Are you sure you want to disable \'%1\' Price Set?', array(1 => $priceSet)); + $ret['content'] = ts('Are you sure you want to disable \'%1\' Price Set?', [1 => $priceSet]); } break; @@ -123,7 +178,7 @@ public static function getStatusMsg() { $ret['content'] = ts('Are you sure you want to disable this CiviCRM Profile field?'); break; - case 'CRM_Contribute_BAO_ManagePremiums': + case 'CRM_Contribute_BAO_Product': $ret['content'] = ts('Are you sure you want to disable this premium? This action will remove the premium from any contribution pages that currently offer it. However it will not delete the premium record - so you can re-enable it and add it back to your contribution page(s) at a later time.'); break; @@ -165,7 +220,7 @@ public static function getStatusMsg() { $ret['content'] = ts('Are you sure you want to disable this Participant Status?') . '

    ' . ts('Users will no longer be able to select this value when adding or editing Participant Status.'); break; - case 'CRM_Mailing_BAO_Component': + case 'CRM_Mailing_BAO_MailingComponent': $ret['content'] = ts('Are you sure you want to disable this component?'); break; @@ -203,6 +258,7 @@ public static function getStatusMsg() { case 'CRM_Contact_BAO_Group': $ret['content'] = ts('Are you sure you want to disable this Group?'); + $ret['content'] .= '

    ' . ts('WARNING - Disabling this group will disable all the child groups associated if any.') . ''; break; case 'CRM_Core_BAO_OptionGroup': @@ -215,7 +271,7 @@ public static function getStatusMsg() { case 'CRM_Core_BAO_OptionValue': $label = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $recordID, 'label'); - $ret['content'] = ts('Are you sure you want to disable the \'%1\' option ?', array(1 => $label)); + $ret['content'] = ts('Are you sure you want to disable the \'%1\' option ?', [1 => $label]); $ret['content'] .= '

    ' . ts('WARNING - Disabling an option which has been assigned to existing records will result in that option being cleared when the record is edited.'); break; @@ -234,7 +290,7 @@ public static function getStatusMsg() { } } else { - $ret = array('status' => 'error', 'content' => 'Error: Unknown entity type.', 'illegal' => TRUE); + $ret = ['status' => 'error', 'content' => 'Error: Unknown entity type.', 'illegal' => TRUE]; } CRM_Core_Page_AJAX::returnJsonResponse($ret); } @@ -244,31 +300,31 @@ public static function getStatusMsg() { * * This appears to be only used by scheduled reminders. */ - static public function mappingList() { + public static function mappingList() { if (empty($_GET['mappingID'])) { - CRM_Utils_JSON::output(array('status' => 'error', 'error_msg' => 'required params missing.')); + CRM_Utils_JSON::output(['status' => 'error', 'error_msg' => 'required params missing.']); } $mapping = CRM_Core_BAO_ActionSchedule::getMapping($_GET['mappingID']); - $dateFieldLabels = $mapping ? $mapping->getDateFields() : array(); + $dateFieldLabels = $mapping ? $mapping->getDateFields() : []; // The UX here is quirky -- for "Activity" types, there's a simple drop "Recipients" // dropdown which is always displayed. For other types, the "Recipients" drop down is // conditional upon the weird isLimit ('Limit To / Also Include / Neither') dropdown. $noThanksJustKidding = !$_GET['isLimit']; if ($mapping instanceof CRM_Activity_ActionMapping || !$noThanksJustKidding) { - $entityRecipientLabels = $mapping ? ($mapping->getRecipientTypes() + CRM_Core_BAO_ActionSchedule::getAdditionalRecipients()) : array(); + $entityRecipientLabels = $mapping ? ($mapping->getRecipientTypes() + CRM_Core_BAO_ActionSchedule::getAdditionalRecipients()) : []; } else { $entityRecipientLabels = CRM_Core_BAO_ActionSchedule::getAdditionalRecipients(); } $recipientMapping = array_combine(array_keys($entityRecipientLabels), array_keys($entityRecipientLabels)); - $output = array( - 'sel4' => CRM_Utils_Array::toKeyValueRows($dateFieldLabels), - 'sel5' => CRM_Utils_Array::toKeyValueRows($entityRecipientLabels), + $output = [ + 'sel4' => CRM_Utils_Array::makeNonAssociative($dateFieldLabels), + 'sel5' => CRM_Utils_Array::makeNonAssociative($entityRecipientLabels), 'recipientMapping' => $recipientMapping, - ); + ]; CRM_Utils_JSON::output($output); } @@ -279,20 +335,20 @@ static public function mappingList() { * Ex: GET /civicrm/ajax/recipientListing?mappingID=contribpage&recipientType= */ public static function recipientListing() { - $mappingID = filter_input(INPUT_GET, 'mappingID', FILTER_VALIDATE_REGEXP, array( - 'options' => array( + $mappingID = filter_input(INPUT_GET, 'mappingID', FILTER_VALIDATE_REGEXP, [ + 'options' => [ 'regexp' => '/^[a-zA-Z0-9_\-]+$/', - ), - )); - $recipientType = filter_input(INPUT_GET, 'recipientType', FILTER_VALIDATE_REGEXP, array( - 'options' => array( + ], + ]); + $recipientType = filter_input(INPUT_GET, 'recipientType', FILTER_VALIDATE_REGEXP, [ + 'options' => [ 'regexp' => '/^[a-zA-Z0-9_\-]+$/', - ), - )); + ], + ]); - CRM_Utils_JSON::output(array( - 'recipients' => CRM_Utils_Array::toKeyValueRows(CRM_Core_BAO_ActionSchedule::getRecipientListing($mappingID, $recipientType)), - )); + CRM_Utils_JSON::output([ + 'recipients' => CRM_Utils_Array::makeNonAssociative(CRM_Core_BAO_ActionSchedule::getRecipientListing($mappingID, $recipientType)), + ]); } /** @@ -302,44 +358,84 @@ public static function recipientListing() { */ public static function getTagTree() { $parent = CRM_Utils_Type::escape(CRM_Utils_Array::value('parent_id', $_GET, 0), 'Integer'); - $result = array(); - - $parentClause = $parent ? "AND tag.parent_id = $parent" : 'AND tag.parent_id IS NULL'; - $sql = "SELECT tag.*, child.id AS child, COUNT(et.id) as usages - FROM civicrm_tag tag - LEFT JOIN civicrm_entity_tag et ON et.tag_id = tag.id - LEFT JOIN civicrm_tag child ON child.parent_id = tag.id - WHERE tag.is_tagset <> 1 $parentClause - GROUP BY tag.id - ORDER BY tag.name"; - $dao = CRM_Core_DAO::executeQuery($sql); + $substring = CRM_Utils_Type::escape(CRM_Utils_Array::value('str', $_GET), 'String'); + $result = []; + + $whereClauses = ['is_tagset <> 1']; + $orderColumn = 'name'; + + // fetch all child tags in Array('parent_tag' => array('child_tag_1', 'child_tag_2', ...)) format + $childTagIDs = CRM_Core_BAO_Tag::getChildTags($substring); + $parentIDs = array_keys($childTagIDs); + + if ($parent) { + $whereClauses[] = "parent_id = $parent"; + } + elseif ($substring) { + $whereClauses['substring'] = " name LIKE '%$substring%' "; + if (!empty($parentIDs)) { + $whereClauses['substring'] = sprintf(" %s OR id IN (%s) ", $whereClauses['substring'], implode(',', $parentIDs)); + } + $orderColumn = 'id'; + } + else { + $whereClauses[] = "parent_id IS NULL"; + } + + $dao = CRM_Utils_SQL_Select::from('civicrm_tag') + ->where($whereClauses) + ->groupBy('id') + ->orderBy($orderColumn) + ->execute(); + while ($dao->fetch()) { - $style = ''; - if ($dao->color) { - $style = "background-color: {$dao->color}; color: " . CRM_Utils_Color::getContrast($dao->color); + if (!empty($substring)) { + $result[] = $dao->id; + if (!empty($childTagIDs[$dao->id])) { + $result = array_merge($result, $childTagIDs[$dao->id]); + } } - $result[] = array( - 'id' => $dao->id, - 'text' => $dao->name, - 'icon' => FALSE, - 'li_attr' => array( - 'title' => ((string) $dao->description) . ($dao->is_reserved ? ' (*' . ts('Reserved') . ')' : ''), - 'class' => $dao->is_reserved ? 'is-reserved' : '', - ), - 'a_attr' => array( - 'style' => $style, - 'class' => 'crm-tag-item', - ), - 'children' => (bool) $dao->child, - 'data' => array( - 'description' => (string) $dao->description, - 'is_selectable' => (bool) $dao->is_selectable, - 'is_reserved' => (bool) $dao->is_reserved, - 'used_for' => $dao->used_for ? explode(',', $dao->used_for) : array(), - 'color' => $dao->color ? $dao->color : '#ffffff', - 'usages' => (int) $dao->usages, - ), - ); + else { + $hasChildTags = empty($childTagIDs[$dao->id]) ? FALSE : TRUE; + $usedFor = (array) explode(',', $dao->used_for); + $tag = [ + 'id' => $dao->id, + 'text' => $dao->name, + 'a_attr' => [ + 'class' => 'crm-tag-item', + ], + 'children' => $hasChildTags, + 'data' => [ + 'description' => (string) $dao->description, + 'is_selectable' => (bool) $dao->is_selectable, + 'is_reserved' => (bool) $dao->is_reserved, + 'used_for' => $usedFor, + 'color' => $dao->color ? $dao->color : '#ffffff', + 'usages' => civicrm_api3('EntityTag', 'getcount', [ + 'entity_table' => ['IN' => $usedFor], + 'tag_id' => $dao->id, + ]), + ], + ]; + if ($dao->description || $dao->is_reserved) { + $tag['li_attr']['title'] = ((string) $dao->description) . ($dao->is_reserved ? ' (*' . ts('Reserved') . ')' : ''); + } + if ($dao->is_reserved) { + $tag['li_attr']['class'] = 'is-reserved'; + } + if ($dao->color) { + $tag['a_attr']['style'] = "background-color: {$dao->color}; color: " . CRM_Utils_Color::getContrast($dao->color); + } + $result[] = $tag; + } + } + + if ($substring) { + $result = array_values(array_unique($result)); + } + + if (!empty($_REQUEST['is_unit_test'])) { + return $result; } CRM_Utils_JSON::output($result); diff --git a/CRM/Admin/Page/APIExplorer.php b/CRM/Admin/Page/APIExplorer.php index c5bfa84a05a3..d0a220cd560b 100644 --- a/CRM/Admin/Page/APIExplorer.php +++ b/CRM/Admin/Page/APIExplorer.php @@ -1,9 +1,9 @@ $rawPath) { + $pathParts = explode(DIRECTORY_SEPARATOR, $rawPath); + foreach ($pathParts as $partId => $part) { + if (empty($part)) { + unset($pathParts[$partId]); + } + } + $newRawPath = implode(DIRECTORY_SEPARATOR, $pathParts); + if ($newRawPath != $rawPath) { + $paths[$id] = DIRECTORY_SEPARATOR . $newRawPath; + } + } + $paths = array_unique($paths); + return $paths; + } + /** * Run page. * @@ -46,17 +69,25 @@ public function run() { ->addScriptFile('civicrm', 'templates/CRM/Admin/Page/APIExplorer.js') ->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js', 99) ->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css', 99) - ->addVars('explorer', array('max_joins' => \Civi\API\Api3SelectQuery::MAX_JOINS)); + ->addVars('explorer', ['max_joins' => \Civi\API\Api3SelectQuery::MAX_JOINS]); $this->assign('operators', CRM_Core_DAO::acceptedSQLOperators()); // List example directories - $examples = array(); - foreach (scandir(\Civi::paths()->getPath('[civicrm.root]/api/v3/examples')) as $item) { - if ($item && strpos($item, '.') === FALSE) { - $examples[] = $item; + // use get_include_path to ensure that extensions are captured. + $examples = []; + $paths = self::uniquePaths(); + foreach ($paths as $path) { + $dir = \CRM_Utils_File::addTrailingSlash($path) . 'api' . DIRECTORY_SEPARATOR . 'v3' . DIRECTORY_SEPARATOR . 'examples'; + if (is_dir($dir)) { + foreach (scandir($dir) as $item) { + if ($item && strpos($item, '.') === FALSE && array_search($item, $examples) === FALSE) { + $examples[] = $item; + } + } } } + sort($examples); $this->assign('examples', $examples); return parent::run(); @@ -77,21 +108,32 @@ public function userContext() { */ public static function getExampleFile() { if (!empty($_GET['entity']) && strpos($_GET['entity'], '.') === FALSE) { - $examples = array(); - foreach (scandir(\Civi::paths()->getPath("[civicrm.root]/api/v3/examples/{$_GET['entity']}")) as $item) { - $item = str_replace('.php', '', $item); - if ($item && strpos($item, '.') === FALSE) { - $examples[] = array('key' => $item, 'value' => $item); + $examples = []; + $paths = self::uniquePaths(); + foreach ($paths as $path) { + $dir = \CRM_Utils_File::addTrailingSlash($path) . 'api' . DIRECTORY_SEPARATOR . 'v3' . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $_GET['entity']; + if (is_dir($dir)) { + foreach (scandir($dir) as $item) { + $item = str_replace('.ex.php', '', $item); + if ($item && strpos($item, '.') === FALSE) { + $examples[] = ['key' => $item, 'value' => $item]; + } + } } } CRM_Utils_JSON::output($examples); } if (!empty($_GET['file']) && strpos($_GET['file'], '.') === FALSE) { - $fileName = \Civi::paths()->getPath("[civicrm.root]/api/v3/examples/{$_GET['file']}.php"); - if (file_exists($fileName)) { - echo file_get_contents($fileName); + $paths = self::uniquePaths(); + $fileFound = FALSE; + foreach ($paths as $path) { + $fileName = \CRM_Utils_File::addTrailingSlash($path) . 'api' . DIRECTORY_SEPARATOR . 'v3' . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $_GET['file'] . '.ex.php'; + if (!$fileFound && file_exists($fileName)) { + $fileFound = TRUE; + echo file_get_contents($fileName); + } } - else { + if (!$fileFound) { echo "Not found."; } CRM_Utils_System::civiExit(); @@ -109,11 +151,11 @@ public static function getDoc() { if (!empty($entity) && in_array($entity, $entities['values']) && strpos($entity, '.') === FALSE) { $action = CRM_Utils_Array::value('action', $_GET); $doc = self::getDocblock($entity, $action); - $result = array( + $result = [ 'doc' => $doc ? self::formatDocBlock($doc[0]) : 'Not found.', 'code' => $doc ? $doc[1] : NULL, 'file' => $doc ? $doc[2] : NULL, - ); + ]; if (!$action) { $actions = civicrm_api3($entity, 'getactions'); $result['actions'] = CRM_Utils_Array::makeNonAssociative(array_combine($actions['values'], $actions['values'])); @@ -141,11 +183,11 @@ private static function getDocBlock($entity, $action) { // Api does not exist return FALSE; } - $docblock = $code = array(); + $docblock = $code = []; // Fetch docblock for the api file if (!$action) { if (preg_match('#/\*\*\n.*?\n \*/\n#s', $contents, $docblock)) { - return array($docblock[0], NULL, $file); + return [$docblock[0], NULL, $file]; } } // Fetch block for a specific action @@ -172,7 +214,7 @@ private static function getDocBlock($entity, $action) { if (preg_match('#(/\*\*(\n \*.*)*\n \*/\n)function[ ]+' . $fnName . '#i', $contents, $docblock)) { // Fetch the code in a separate regex to preserve sanity preg_match("#^function[ ]+$fnName.*?^}#ism", $contents, $code); - return array($docblock[1], $code[0], $file); + return [$docblock[1], $code[0], $file]; } } } @@ -191,13 +233,13 @@ public static function formatDocBlock($text) { $text = implode("\n", $lines); // Get rid of comment stars - $text = str_replace(array("\n * ", "\n *\n", "\n */\n", "/**\n"), array("\n", "\n\n", '', ''), $text); + $text = str_replace(["\n * ", "\n *\n", "\n */\n", "/**\n"], ["\n", "\n\n", '', ''], $text); // Format for html $text = htmlspecialchars($text); // Extract code blocks - save for later to skip html conversion - $code = array(); + $code = []; preg_match_all('#@code(.*?)@endcode#is', $text, $code); $text = preg_replace('#@code.*?@endcode#is', '
    ', $text);
     
    diff --git a/CRM/Admin/Page/Access.php b/CRM/Admin/Page/Access.php
    index 5e493932a271..9d016ae5a19f 100644
    --- a/CRM/Admin/Page/Access.php
    +++ b/CRM/Admin/Page/Access.php
    @@ -1,9 +1,9 @@
     assign('registerSite', htmlspecialchars('https://civicrm.org/register-your-site?src=iam&sid=' . CRM_Utils_System::getSiteID()));
     
    -    $groups = array(
    +    $groups = [
           'Customize Data and Screens' => ts('Customize Data and Screens'),
           'Communications' => ts('Communications'),
           'Localization' => ts('Localization'),
           'Users and Permissions' => ts('Users and Permissions'),
           'System Settings' => ts('System Settings'),
    -    );
    +    ];
     
         $config = CRM_Core_Config::singleton();
         if (in_array('CiviContribute', $config->enableComponents)) {
    @@ -98,12 +99,14 @@ public function run() {
             $adminPanel[$groupId]['title'] = $title;
           }
           else {
    -        $adminPanel[$groupId] = array();
    +        $adminPanel[$groupId] = [];
             $adminPanel[$groupId]['show'] = '';
             $adminPanel[$groupId]['hide'] = '';
             $adminPanel[$groupId]['title'] = $title;
           }
         }
    +
    +    CRM_Utils_Hook::alterAdminPanel($adminPanel);
         $this->assign('adminPanel', $adminPanel);
         $this->_showHide->addToTemplate();
         return parent::run();
    diff --git a/CRM/Admin/Page/CKEditorConfig.php b/CRM/Admin/Page/CKEditorConfig.php
    index c0e29be43525..02328983d7cf 100644
    --- a/CRM/Admin/Page/CKEditorConfig.php
    +++ b/CRM/Admin/Page/CKEditorConfig.php
    @@ -1,9 +1,9 @@
     addScriptFile('civicrm', 'js/wysiwyg/admin.ckeditor-configurator.js', 10)
           ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/toolbarconfigurator/css/fontello.css')
           ->addStyleFile('civicrm', 'bower_components/ckeditor/samples/css/samples.css')
    -      ->addVars('ckConfig', array(
    +      ->addVars('ckConfig', [
             'plugins' => array_values($this->getCKPlugins()),
             'blacklist' => $this->blackList,
             'settings' => $settings,
    -      ));
    +      ]);
     
    -    $configUrl = self::getConfigUrl($this->preset);
    -    if (!$configUrl) {
    -      $configUrl = self::getConfigUrl('default');
    -    }
    +    $configUrl = self::getConfigUrl($this->preset) ?: self::getConfigUrl('default');
     
         $this->assign('preset', $this->preset);
         $this->assign('presets', CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'));
    @@ -107,12 +104,14 @@ public function run() {
         $this->assign('skin', CRM_Utils_Array::value('skin', $settings));
         $this->assign('extraPlugins', CRM_Utils_Array::value('extraPlugins', $settings));
         $this->assign('configUrl', $configUrl);
    -    $this->assign('revertConfirm', htmlspecialchars(ts('Are you sure you want to revert all changes?', array('escape' => 'js'))));
    +    $this->assign('revertConfirm', htmlspecialchars(ts('Are you sure you want to revert all changes?', ['escape' => 'js'])));
     
    -    CRM_Utils_System::appendBreadCrumb(array(array(
    -      'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'),
    -      'title' => ts('Display Preferences'),
    -    )));
    +    CRM_Utils_System::appendBreadCrumb([
    +      [
    +        'url' => CRM_Utils_System::url('civicrm/admin/setting/preferences/display', 'reset=1'),
    +        'title' => ts('Display Preferences'),
    +      ],
    +    ]);
     
         return parent::run();
       }
    @@ -132,7 +131,13 @@ public function save($params) {
           $val = trim($val);
           if (strpos($key, 'config_') === 0 && strlen($val)) {
             if ($val != 'true' && $val != 'false' && $val != 'null' && $val[0] != '{' && $val[0] != '[' && !is_numeric($val)) {
    -          $val = json_encode($val);
    +          $val = json_encode($val, JSON_UNESCAPED_SLASHES);
    +        }
    +        elseif ($val[0] == '{' || $val[0] == '[') {
    +          if (!is_array(json_decode($val, TRUE))) {
    +            // Invalid JSON. Do not save.
    +            continue;
    +          }
             }
             $pos = strrpos($config, '};');
             $key = preg_replace('/^config_/', 'config.', $key);
    @@ -152,19 +157,19 @@ public function save($params) {
        * @return array
        */
       private function getCKPlugins() {
    -    $plugins = array();
    +    $plugins = [];
         $pluginDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/plugins');
     
         foreach (glob($pluginDir . '/*', GLOB_ONLYDIR) as $dir) {
           $dir = rtrim(str_replace('\\', '/', $dir), '/');
           $name = substr($dir, strrpos($dir, '/') + 1);
    -      $dir = CRM_Utils_file::addTrailingSlash($dir, '/');
    +      $dir = CRM_Utils_File::addTrailingSlash($dir, '/');
           if (is_file($dir . 'plugin.js')) {
    -        $plugins[$name] = array(
    +        $plugins[$name] = [
               'id' => $name,
               'text' => ucfirst($name),
               'icon' => NULL,
    -        );
    +        ];
             if (is_dir($dir . "icons")) {
               if (is_file($dir . "icons/$name.png")) {
                 $plugins[$name]['icon'] = "bower_components/ckeditor/plugins/$name/icons/$name.png";
    @@ -187,7 +192,7 @@ private function getCKPlugins() {
        * @return array
        */
       private function getCKSkins() {
    -    $skins = array();
    +    $skins = [];
         $skinDir = Civi::paths()->getPath('[civicrm.root]/bower_components/ckeditor/skins');
         foreach (glob($skinDir . '/*', GLOB_ONLYDIR) as $dir) {
           $dir = rtrim(str_replace('\\', '/', $dir), '/');
    @@ -200,11 +205,8 @@ private function getCKSkins() {
        * @return array
        */
       private function getConfigSettings() {
    -    $matches = $result = array();
    -    $file = self::getConfigFile($this->preset);
    -    if (!$file) {
    -      $file = self::getConfigFile('default');
    -    }
    +    $matches = $result = [];
    +    $file = self::getConfigFile($this->preset) ?: self::getConfigFile('default');
         $result['skin'] = 'moono';
         if ($file) {
           $contents = file_get_contents($file);
    @@ -222,7 +224,7 @@ private function getConfigSettings() {
        * @return array|null|string
        */
       public static function getConfigUrl($preset = NULL) {
    -    $items = array();
    +    $items = [];
         $presets = CRM_Core_OptionGroup::values('wysiwyg_presets', FALSE, FALSE, FALSE, NULL, 'name');
         foreach ($presets as $key => $name) {
           if (self::getConfigFile($name)) {
    @@ -243,6 +245,7 @@ public static function getConfigFile($preset = 'default') {
       }
     
       /**
    +   * @param string $preset
        * @param string $contents
        */
       public static function saveConfigFile($preset, $contents) {
    @@ -270,7 +273,7 @@ public static function setConfigDefault() {
           if (!is_dir(Civi::paths()->getPath('[civicrm.files]/persist'))) {
             mkdir(Civi::paths()->getPath('[civicrm.files]/persist'));
           }
    -      $newFileName = Civi::paths()->getPath('[civicrm.files]/persist/crm-ckeditor-default.js');
    +      $newFileName = Civi::paths()->getPath(self::CONFIG_FILEPATH . 'default.js');
           file_put_contents($newFileName, $config);
         }
       }
    diff --git a/CRM/Admin/Page/ConfigTaskList.php b/CRM/Admin/Page/ConfigTaskList.php
    index e42fa94b7ef9..ccb987294b94 100644
    --- a/CRM/Admin/Page/ConfigTaskList.php
    +++ b/CRM/Admin/Page/ConfigTaskList.php
    @@ -1,9 +1,9 @@
     assign('registerSite', htmlspecialchars('https://civicrm.org/register-your-site?src=iam&sid=' . CRM_Utils_System::getSiteID()));
     
    +    //Provide ability to optionally display some component checklist items when components are on
    +    $result = civicrm_api3('Setting', 'get', [
    +      'sequential' => 1,
    +      'return' => ["enable_components"],
    +    ]);
    +    $enabled = array();
    +    foreach ($result['values'][0]['enable_components'] as $component) {
    +      $enabled[$component] = 1;
    +    }
    +    $this->assign('enabledComponents', $enabled);
    +
         return parent::run();
       }
     
    diff --git a/CRM/Admin/Page/ContactType.php b/CRM/Admin/Page/ContactType.php
    index c11e4a356d2b..b7d4dc2af287 100644
    --- a/CRM/Admin/Page/ContactType.php
    +++ b/CRM/Admin/Page/ContactType.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/options/subtype',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Contact Type'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Contact Type'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Contact Type'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/options/subtype',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Contact Type'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    @@ -124,7 +124,7 @@ public function browse() {
             }
           }
           $rows[$key]['action'] = CRM_Core_Action::formLink(self::links(), $mask,
    -        array('id' => $value['id']),
    +        ['id' => $value['id']],
             ts('more'),
             FALSE,
             'contactType.manage.action',
    diff --git a/CRM/Admin/Page/EventTemplate.php b/CRM/Admin/Page/EventTemplate.php
    index 72be36e773aa..049b4d65667c 100644
    --- a/CRM/Admin/Page/EventTemplate.php
    +++ b/CRM/Admin/Page/EventTemplate.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/event/manage/settings',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Event Template'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/event/manage',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Event Template'),
    -        ),
    -      );
    +        ],
    +      ];
         }
     
         return self::$_links;
    @@ -86,7 +86,7 @@ public function &links() {
        */
       public function browse() {
         //get all event templates.
    -    $allEventTemplates = array();
    +    $allEventTemplates = [];
     
         $eventTemplate = new CRM_Event_DAO_Event();
     
    @@ -120,7 +120,7 @@ public function browse() {
     
           //add action links.
           $allEventTemplates[$eventTemplate->id]['action'] = CRM_Core_Action::formLink(self::links(), $action,
    -        array('id' => $eventTemplate->id),
    +        ['id' => $eventTemplate->id],
             ts('more'),
             FALSE,
             'eventTemplate.manage.action',
    diff --git a/CRM/Admin/Page/Extensions.php b/CRM/Admin/Page/Extensions.php
    index 589b9dfde6d5..40bd1bfbbe1e 100644
    --- a/CRM/Admin/Page/Extensions.php
    +++ b/CRM/Admin/Page/Extensions.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::ADD => [
               'name' => ts('Install'),
               'url' => 'civicrm/admin/extensions',
               'qs' => 'action=add&id=%%id%%&key=%%key%%',
               'title' => ts('Install'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'url' => 'civicrm/admin/extensions',
               'qs' => 'action=enable&id=%%id%%&key=%%key%%',
               'ref' => 'enable-action',
               'title' => ts('Enable'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'url' => 'civicrm/admin/extensions',
               'qs' => 'action=disable&id=%%id%%&key=%%key%%',
               'title' => ts('Disable'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Uninstall'),
               'url' => 'civicrm/admin/extensions',
               'qs' => 'action=delete&id=%%id%%&key=%%key%%',
               'title' => ts('Uninstall Extension'),
    -        ),
    -        CRM_Core_Action::UPDATE => array(
    +        ],
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Download'),
               'url' => 'civicrm/admin/extensions',
               'qs' => 'action=update&id=%%id%%&key=%%key%%',
               'title' => ts('Download Extension'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    @@ -125,8 +125,6 @@ public function run() {
        * Browse all options.
        */
       public function browse() {
    -    $mapper = CRM_Extension_System::singleton()->getMapper();
    -    $manager = CRM_Extension_System::singleton()->getManager();
     
         // build announcements at the top of the page
         $this->assign('extAddNewEnabled', CRM_Extension_System::singleton()->getBrowser()->isEnabled());
    @@ -145,8 +143,25 @@ public function browse() {
         // TODO: Debate whether to immediately detect changes in underlying source tree
         // $manager->refresh();
     
    -    // build list of local extensions
    -    $localExtensionRows = array(); // array($pseudo_id => extended_CRM_Extension_Info)
    +    $localExtensionRows = $this->formatLocalExtensionRows();
    +    $this->assign('localExtensionRows', $localExtensionRows);
    +
    +    $remoteExtensionRows = $this->formatRemoteExtensionRows($localExtensionRows);
    +    $this->assign('remoteExtensionRows', $remoteExtensionRows);
    +  }
    +
    +  /**
    +   * Get the list of local extensions and format them as a table with
    +   * status and action data.
    +   *
    +   * @return array
    +   */
    +  public function formatLocalExtensionRows() {
    +    $mapper = CRM_Extension_System::singleton()->getMapper();
    +    $manager = CRM_Extension_System::singleton()->getManager();
    +
    +    // array($pseudo_id => extended_CRM_Extension_Info)
    +    $localExtensionRows = [];
         $keys = array_keys($manager->getStatuses());
         sort($keys);
         foreach ($keys as $key) {
    @@ -154,22 +169,27 @@ public function browse() {
             $obj = $mapper->keyToInfo($key);
           }
           catch (CRM_Extension_Exception $ex) {
    -        CRM_Core_Session::setStatus(ts('Failed to read extension (%1). Please refresh the extension list.', array(1 => $key)));
    +        CRM_Core_Session::setStatus(ts('Failed to read extension (%1). Please refresh the extension list.', [1 => $key]));
             continue;
           }
     
           $row = self::createExtendedInfo($obj);
           $row['id'] = $obj->key;
    +      $row['action'] = '';
     
           // assign actions
           $action = 0;
           switch ($row['status']) {
             case CRM_Extension_Manager::STATUS_UNINSTALLED:
    -          $action += CRM_Core_Action::ADD;
    +          if (!$manager->isIncompatible($row['id'])) {
    +            $action += CRM_Core_Action::ADD;
    +          }
               break;
     
             case CRM_Extension_Manager::STATUS_DISABLED:
    -          $action += CRM_Core_Action::ENABLE;
    +          if (!$manager->isIncompatible($row['id'])) {
    +            $action += CRM_Core_Action::ENABLE;
    +          }
               $action += CRM_Core_Action::DELETE;
               break;
     
    @@ -186,45 +206,58 @@ public function browse() {
           }
           // TODO if extbrowser is enabled and extbrowser has newer version than extcontainer,
           // then $action += CRM_Core_Action::UPDATE
    -      $row['action'] = CRM_Core_Action::formLink(self::links(),
    -        $action,
    -        array(
    -          'id' => $row['id'],
    -          'key' => $obj->key,
    -        ),
    -        ts('more'),
    -        FALSE,
    -        'extension.local.action',
    -        'Extension',
    -        $row['id']
    -      );
    +      if ($action) {
    +        $row['action'] = CRM_Core_Action::formLink(self::links(),
    +          $action,
    +          ['id' => $row['id'], 'key' => $obj->key],
    +          ts('more'),
    +          FALSE,
    +          'extension.local.action',
    +          'Extension',
    +          $row['id']
    +        );
    +      }
           // Key would be better to send, but it's not an integer.  Moreover, sending the
           // values to hook_civicrm_links means that you can still get at the key
     
           $localExtensionRows[$row['id']] = $row;
         }
    -    $this->assign('localExtensionRows', $localExtensionRows);
    +    return $localExtensionRows;
    +  }
     
    +  /**
    +   * Get the list of remote extensions and format them as a table with
    +   * status and action data.
    +   *
    +   * @param array $localExtensionRows
    +   * @return array
    +   */
    +  public function formatRemoteExtensionRows($localExtensionRows) {
         try {
           $remoteExtensions = CRM_Extension_System::singleton()->getBrowser()->getExtensions();
         }
         catch (CRM_Extension_Exception $e) {
    -      $remoteExtensions = array();
    +      $remoteExtensions = [];
           CRM_Core_Session::setStatus($e->getMessage(), ts('Extension download error'), 'error');
         }
     
         // build list of available downloads
    -    $remoteExtensionRows = array();
    +    $remoteExtensionRows = [];
    +    $compat = CRM_Extension_System::getCompatibilityInfo();
    +
         foreach ($remoteExtensions as $info) {
    +      if (!empty($compat[$info->key]['obsolete'])) {
    +        continue;
    +      }
           $row = (array) $info;
           $row['id'] = $info->key;
           $action = CRM_Core_Action::UPDATE;
           $row['action'] = CRM_Core_Action::formLink(self::links(),
             $action,
    -        array(
    +        [
               'id' => $row['id'],
               'key' => $row['key'],
    -        ),
    +        ],
             ts('more'),
             FALSE,
             'extension.remote.action',
    @@ -232,13 +265,16 @@ public function browse() {
             $row['id']
           );
           if (isset($localExtensionRows[$info->key])) {
    -        if (version_compare($localExtensionRows[$info->key]['version'], $info->version, '<')) {
    -          $row['is_upgradeable'] = TRUE;
    +        if (array_key_exists('version', $localExtensionRows[$info->key])) {
    +          if (version_compare($localExtensionRows[$info->key]['version'], $info->version, '<')) {
    +            $row['is_upgradeable'] = TRUE;
    +          }
             }
           }
           $remoteExtensionRows[$row['id']] = $row;
         }
    -    $this->assign('remoteExtensionRows', $remoteExtensionRows);
    +
    +    return $remoteExtensionRows;
       }
     
       /**
    diff --git a/CRM/Admin/Page/ExtensionsUpgrade.php b/CRM/Admin/Page/ExtensionsUpgrade.php
    index b0a2a8e7ec15..3a183826fc25 100644
    --- a/CRM/Admin/Page/ExtensionsUpgrade.php
    +++ b/CRM/Admin/Page/ExtensionsUpgrade.php
    @@ -15,16 +15,17 @@ class CRM_Admin_Page_ExtensionsUpgrade extends CRM_Core_Page {
        */
       public function run() {
         $queue = CRM_Extension_Upgrades::createQueue();
    -    $runner = new CRM_Queue_Runner(array(
    +    $runner = new CRM_Queue_Runner([
           'title' => ts('Database Upgrades'),
           'queue' => $queue,
           'errorMode' => CRM_Queue_Runner::ERROR_ABORT,
    -      'onEnd' => array('CRM_Admin_Page_ExtensionsUpgrade', 'onEnd'),
    +      'onEnd' => ['CRM_Admin_Page_ExtensionsUpgrade', 'onEnd'],
           'onEndUrl' => !empty($_GET['destination']) ? $_GET['destination'] : CRM_Utils_System::url(self::END_URL, self::END_PARAMS),
    -    ));
    +    ]);
     
         CRM_Core_Error::debug_log_message('CRM_Admin_Page_ExtensionsUpgrade: Start upgrades');
    -    $runner->runAllViaWeb(); // does not return
    +    // does not return
    +    $runner->runAllViaWeb();
       }
     
       /**
    diff --git a/CRM/Admin/Page/Job.php b/CRM/Admin/Page/Job.php
    index ce8ab72ddc6a..9fdc42cc44be 100644
    --- a/CRM/Admin/Page/Job.php
    +++ b/CRM/Admin/Page/Job.php
    @@ -1,9 +1,9 @@
      'action=delete&id=%%id%%',
               'title' => ts('Delete Scheduled Job'),
             ),
    +        CRM_Core_Action::COPY => array(
    +          'name' => ts('Copy'),
    +          'url' => 'civicrm/admin/job',
    +          'qs' => 'action=copy&id=%%id%%',
    +          'title' => ts('Copy Scheduled Job'),
    +        ),
           );
         }
         return self::$_links;
    @@ -128,11 +134,25 @@ public function run() {
           $this, FALSE, 0
         );
     
    +    // FIXME: Why are we comparing an integer with a string here?
         if ($this->_action == 'export') {
           $session = CRM_Core_Session::singleton();
           $session->pushUserContext(CRM_Utils_System::url('civicrm/admin/job', 'reset=1'));
         }
     
    +    if (($this->_action & CRM_Core_Action::COPY) && (!empty($this->_id))) {
    +      try {
    +        $jobResult = civicrm_api3('Job', 'clone', array('id' => $this->_id));
    +        if ($jobResult['count'] > 0) {
    +          CRM_Core_Session::setStatus($jobResult['values'][$jobResult['id']]['name'], ts('Job copied successfully'), 'success');
    +        }
    +        CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/job', 'reset=1'));
    +      }
    +      catch (Exception $e) {
    +        CRM_Core_Session::setStatus(ts('Failed to copy job'), 'Error');
    +      }
    +    }
    +
         return parent::run();
       }
     
    @@ -142,6 +162,10 @@ public function run() {
        * @param null $action
        */
       public function browse($action = NULL) {
    +    // check if non-prod mode is enabled.
    +    if (CRM_Core_Config::environment() != 'Production') {
    +      CRM_Core_Session::setStatus(ts('Execution of scheduled jobs has been turned off by default since this is a non-production environment. You can override this for particular jobs by adding runInNonProductionEnvironment=TRUE as a parameter.'), ts("Non-production Environment"), "warning", array('expires' => 0));
    +    }
     
         // using Export action for Execute. Doh.
         if ($this->_action & CRM_Core_Action::EXPORT) {
    diff --git a/CRM/Admin/Page/JobLog.php b/CRM/Admin/Page/JobLog.php
    index e3128511d05b..b71ba3ab4d41 100644
    --- a/CRM/Admin/Page/JobLog.php
    +++ b/CRM/Admin/Page/JobLog.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/labelFormats',
               'qs' => 'action=update&id=%%id%%&group=%%group%%&reset=1',
               'title' => ts('Edit Label Format'),
    -        ),
    -        CRM_Core_Action::COPY => array(
    +        ],
    +        CRM_Core_Action::COPY => [
               'name' => ts('Copy'),
               'url' => 'civicrm/admin/labelFormats',
               'qs' => 'action=copy&id=%%id%%&group=%%group%%&reset=1',
               'title' => ts('Copy Label Format'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/labelFormats',
               'qs' => 'action=delete&id=%%id%%&group=%%group%%&reset=1',
               'title' => ts('Delete Label Format'),
    -        ),
    -      );
    +        ],
    +      ];
         }
     
         return self::$_links;
    @@ -141,7 +141,7 @@ public function browse($action = NULL) {
     
           $format['groupName'] = ts('Mailing Label');
           $format['action'] = CRM_Core_Action::formLink(self::links(), $action,
    -        array('id' => $format['id'], 'group' => 'label_format'),
    +        ['id' => $format['id'], 'group' => 'label_format'],
             ts('more'),
             FALSE,
             'labelFormat.manage.action',
    diff --git a/CRM/Admin/Page/LocationType.php b/CRM/Admin/Page/LocationType.php
    index 32c7af03c019..f22c00800a7b 100644
    --- a/CRM/Admin/Page/LocationType.php
    +++ b/CRM/Admin/Page/LocationType.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/locationType',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Location Type'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Location Type'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Location Type'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/locationType',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Location Type'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Admin/Page/MailSettings.php b/CRM/Admin/Page/MailSettings.php
    index a2969ce83ef7..5a95c855e04d 100644
    --- a/CRM/Admin/Page/MailSettings.php
    +++ b/CRM/Admin/Page/MailSettings.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/mailSettings',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Mail Settings'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/mailSettings',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Mail Settings'),
    -        ),
    -      );
    +        ],
    +      ];
         }
     
         return self::$_links;
    @@ -88,7 +88,7 @@ public function &links() {
        */
       public function browse() {
         //get all mail settings.
    -    $allMailSettings = array();
    +    $allMailSettings = [];
         $mailSetting = new CRM_Core_DAO_MailSettings();
     
         $allProtocols = CRM_Core_PseudoConstant::get('CRM_Core_DAO_MailSettings', 'protocol');
    @@ -113,7 +113,7 @@ public function browse() {
     
           //add action links.
           $allMailSettings[$mailSetting->id]['action'] = CRM_Core_Action::formLink(self::links(), $action,
    -        array('id' => $mailSetting->id),
    +        ['id' => $mailSetting->id],
             ts('more'),
             FALSE,
             'mailSetting.manage.action',
    diff --git a/CRM/Admin/Page/Mapping.php b/CRM/Admin/Page/Mapping.php
    index 3b690fe4d9be..0da215d2cc2c 100644
    --- a/CRM/Admin/Page/Mapping.php
    +++ b/CRM/Admin/Page/Mapping.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/mapping',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Mapping'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/mapping',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Mapping'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Admin/Page/MessageTemplates.php b/CRM/Admin/Page/MessageTemplates.php
    index d94ffc020ec0..b81a56a0a02e 100644
    --- a/CRM/Admin/Page/MessageTemplates.php
    +++ b/CRM/Admin/Page/MessageTemplates.php
    @@ -1,9 +1,9 @@
      'js')) . '\n\n' . ts('We recommend that you save a copy of the your customized Text and HTML message content to a text file before reverting so you can combine your changes with the system default messages as needed.', array('escape' => 'js'));
    -      self::$_links = array(
    -        CRM_Core_Action::UPDATE => array(
    +      $confirm = ts('Are you sure you want to revert this template to the default for this workflow? You will lose any customizations you have made.', ['escape' => 'js']) . '\n\n' . ts('We recommend that you save a copy of the your customized Text and HTML message content to a text file before reverting so you can combine your changes with the system default messages as needed.', ['escape' => 'js']);
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/messageTemplates/add',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit this message template'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable this message template'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable this message template'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/messageTemplates',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete this message template'),
    -        ),
    -        CRM_Core_Action::REVERT => array(
    +        ],
    +        CRM_Core_Action::REVERT => [
               'name' => ts('Revert to Default'),
               'extra' => "onclick = 'return confirm(\"$confirm\");'",
               'url' => 'civicrm/admin/messageTemplates',
               'qs' => 'action=revert&id=%%id%%&selectedChild=workflow',
               'title' => ts('Revert this workflow message template to the system default'),
    -        ),
    -        CRM_Core_Action::VIEW => array(
    +        ],
    +        CRM_Core_Action::VIEW => [
               'name' => ts('View Default'),
               'url' => 'civicrm/admin/messageTemplates',
               'qs' => 'action=view&id=%%orig_id%%&reset=1',
               'title' => ts('View the system default for this workflow message template'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    @@ -161,10 +167,11 @@ public function action(&$object, $action, &$values, &$links, $permission, $force
           }
     
           // rebuild the action links HTML, as we need to handle %%orig_id%% for revertible templates
    -      $values['action'] = CRM_Core_Action::formLink($links, $action, array(
    +      $values['action'] = CRM_Core_Action::formLink($links, $action,
    +        [
               'id' => $object->id,
               'orig_id' => CRM_Utils_Array::value($object->id, $this->_revertible),
    -        ),
    +        ],
             ts('more'),
             FALSE,
             'messageTemplate.manage.action',
    @@ -187,21 +194,13 @@ public function action(&$object, $action, &$values, &$links, $permission, $force
        * @throws Exception
        */
       public function run($args = NULL, $pageArgs = NULL, $sort = NULL) {
    +    $id = $this->getIdAndAction();
         // handle the revert action and offload the rest to parent
    -    if (CRM_Utils_Request::retrieve('action', 'String', $this) & CRM_Core_Action::REVERT) {
    -
    -      $id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
    -      if (!$this->checkPermission($id, NULL)) {
    -        CRM_Core_Error::fatal(ts('You do not have permission to revert this template.'));
    -      }
    -
    +    if ($this->_action & CRM_Core_Action::REVERT) {
           $this->_revertedId = $id;
    -
           CRM_Core_BAO_MessageTemplate::revert($id);
         }
     
    -    $this->assign('selectedChild', CRM_Utils_Request::retrieve('selectedChild', 'String', $this));
    -
         return parent::run($args, $pageArgs, $sort);
       }
     
    @@ -263,13 +262,13 @@ public function browse() {
         $messageTemplate = new CRM_Core_BAO_MessageTemplate();
         $messageTemplate->orderBy('msg_title' . ' asc');
     
    -    $userTemplates = array();
    -    $workflowTemplates = array();
    +    $userTemplates = [];
    +    $workflowTemplates = [];
     
         // find all objects
         $messageTemplate->find();
         while ($messageTemplate->fetch()) {
    -      $values[$messageTemplate->id] = array();
    +      $values[$messageTemplate->id] = [];
           CRM_Core_DAO::storeValues($messageTemplate, $values[$messageTemplate->id]);
           // populate action links
           $this->action($messageTemplate, $action, $values[$messageTemplate->id], $links, CRM_Core_Permission::EDIT);
    @@ -282,12 +281,15 @@ public function browse() {
           }
         }
     
    -    $rows = array(
    +    $rows = [
           'userTemplates' => $userTemplates,
           'workflowTemplates' => $workflowTemplates,
    -    );
    +    ];
     
         $this->assign('rows', $rows);
    +    $this->assign('canEditSystemTemplates', CRM_Core_Permission::check('edit system workflow message templates'));
    +    $this->assign('canEditMessageTemplates', CRM_Core_Permission::check('edit message templates'));
    +    $this->assign('canEditUserDrivenMessageTemplates', CRM_Core_Permission::check('edit user-driven message templates'));
       }
     
     }
    diff --git a/CRM/Admin/Page/Navigation.php b/CRM/Admin/Page/Navigation.php
    index d22d8be7cd22..30234dacfca8 100644
    --- a/CRM/Admin/Page/Navigation.php
    +++ b/CRM/Admin/Page/Navigation.php
    @@ -1,9 +1,9 @@
     urlPath[3])) {
           self::$_gName = $this->urlPath[3];
    +      self::$_isLocked = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', self::$_gName, 'is_locked', 'name');
         }
         // If an id arg is passed instead of a group name in the path
         elseif (!self::$_gName && !empty($_GET['gid'])) {
    @@ -151,6 +152,7 @@ public function preProcess() {
           $this->assign('showCounted', TRUE);
         }
         $this->assign('isLocked', self::$_isLocked);
    +    $this->assign('allowLoggedIn', Civi::settings()->get('allow_mail_from_logged_in_contact'));
         $config = CRM_Core_Config::singleton();
         if (self::$_gName == 'activity_type') {
           $this->assign('showComponent', TRUE);
    diff --git a/CRM/Admin/Page/ParticipantStatusType.php b/CRM/Admin/Page/ParticipantStatusType.php
    index 899271b9f22e..7f0202c3a4a1 100644
    --- a/CRM/Admin/Page/ParticipantStatusType.php
    +++ b/CRM/Admin/Page/ParticipantStatusType.php
    @@ -1,9 +1,9 @@
      array(
    +      $links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/participant_status',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Status'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/participant_status',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Status'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Status'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Status'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return $links;
       }
     
       public function browse() {
    -    $statusTypes = array();
    +    $statusTypes = [];
     
         $dao = new CRM_Event_DAO_ParticipantStatusType();
         $dao->orderBy('weight');
    @@ -87,13 +87,13 @@ public function browse() {
         $visibilities = CRM_Core_PseudoConstant::visibility();
     
         // these statuses are reserved, but disabled by default - so should be disablable after being enabled
    -    $disablable = array(
    +    $disablable = [
           'On waitlist',
           'Awaiting approval',
           'Pending from waitlist',
           'Pending from approval',
           'Rejected',
    -    );
    +    ];
     
         while ($dao->fetch()) {
           CRM_Core_DAO::storeValues($dao, $statusTypes[$dao->id]);
    @@ -108,7 +108,7 @@ public function browse() {
           $statusTypes[$dao->id]['action'] = CRM_Core_Action::formLink(
             self::links(),
             $action,
    -        array('id' => $dao->id),
    +        ['id' => $dao->id],
             ts('more'),
             FALSE,
             'participantStatusType.manage.action',
    diff --git a/CRM/Admin/Page/PaymentProcessor.php b/CRM/Admin/Page/PaymentProcessor.php
    index 63c9e1917607..3ed9836bd18b 100644
    --- a/CRM/Admin/Page/PaymentProcessor.php
    +++ b/CRM/Admin/Page/PaymentProcessor.php
    @@ -1,9 +1,9 @@
      'name',
    -        'flip' => 1,
    -      ));
    +      'labelColumn' => 'name',
    +      'flip' => 1,
    +    ));
         $this->assign('defaultPaymentProcessorType', $paymentProcessorTypes['PayPal']);
         $breadCrumb = array(
           array(
    @@ -134,8 +134,9 @@ public function browse($action = NULL) {
         while ($dao->fetch()) {
           $paymentProcessor[$dao->id] = array();
           CRM_Core_DAO::storeValues($dao, $paymentProcessor[$dao->id]);
    -      $paymentProcessor[$dao->id]['payment_processor_type'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType',
    -        $paymentProcessor[$dao->id]['payment_processor_type_id']);
    +      $paymentProcessor[$dao->id]['payment_processor_type'] = CRM_Core_PseudoConstant::getLabel(
    +        'CRM_Financial_DAO_PaymentProcessor', 'payment_processor_type_id', $dao->payment_processor_type_id
    +      );
     
           // form all action links
           $action = array_sum(array_keys($this->links()));
    @@ -157,6 +158,13 @@ public function browse($action = NULL) {
             $dao->id
           );
           $paymentProcessor[$dao->id]['financialAccount'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($dao->id, NULL, 'civicrm_payment_processor', 'financial_account_id.name');
    +
    +      try {
    +        $paymentProcessor[$dao->id]['test_id'] = CRM_Financial_BAO_PaymentProcessor::getTestProcessorId($dao->id);
    +      }
    +      catch (CiviCRM_API3_Exception $e) {
    +        CRM_Core_Session::setStatus(ts('No test processor entry exists for %1. Not having a test entry for each processor could cause problems', [$dao->name]));
    +      }
         }
     
         $this->assign('rows', $paymentProcessor);
    diff --git a/CRM/Admin/Page/PaymentProcessorType.php b/CRM/Admin/Page/PaymentProcessorType.php
    index 66239e578a9e..ab366bd54f89 100644
    --- a/CRM/Admin/Page/PaymentProcessorType.php
    +++ b/CRM/Admin/Page/PaymentProcessorType.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/paymentProcessorType',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Payment ProcessorType'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Payment ProcessorType'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Payment ProcessorType'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/paymentProcessorType',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Payment ProcessorType'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Admin/Page/PdfFormats.php b/CRM/Admin/Page/PdfFormats.php
    index 1c86644879d9..24abb2921207 100644
    --- a/CRM/Admin/Page/PdfFormats.php
    +++ b/CRM/Admin/Page/PdfFormats.php
    @@ -1,7 +1,7 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/pdfFormats',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit PDF Page Format'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/pdfFormats',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete PDF Page Format'),
    -        ),
    -      );
    +        ],
    +      ];
         }
     
         return self::$_links;
    @@ -131,7 +131,7 @@ public function browse($action = NULL) {
           $format['action'] = CRM_Core_Action::formLink(
             self::links(),
             $action,
    -        array('id' => $format['id']),
    +        ['id' => $format['id']],
             ts('more'),
             FALSE,
             'pdfFormat.manage.action',
    diff --git a/CRM/Admin/Page/Persistent.php b/CRM/Admin/Page/Persistent.php
    index 5fe710b75c8d..4524da544492 100644
    --- a/CRM/Admin/Page/Persistent.php
    +++ b/CRM/Admin/Page/Persistent.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_stringActionLinks = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/tplstrings/add',
               'qs' => 'reset=1&action=update&id=%%id%%',
               'title' => ts('Configure'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_stringActionLinks;
       }
    @@ -73,14 +73,14 @@ public function &customizeActionLinks() {
         // check if variable _actionsLinks is populated
         if (!isset(self::$_customizeActionLinks)) {
     
    -      self::$_customizeActionLinks = array(
    -        CRM_Core_Action::UPDATE => array(
    +      self::$_customizeActionLinks = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/tplstrings/add',
               'qs' => 'reset=1&action=update&id=%%id%%&config=1',
               'title' => ts('Configure'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_customizeActionLinks;
       }
    @@ -107,14 +107,14 @@ public function browse() {
     
         $daoResult = new CRM_Core_DAO_Persistent();
         $daoResult->find();
    -    $schoolValues = array();
    +    $schoolValues = [];
         while ($daoResult->fetch()) {
    -      $values[$daoResult->id] = array();
    +      $values[$daoResult->id] = [];
           CRM_Core_DAO::storeValues($daoResult, $values[$daoResult->id]);
           if ($daoResult->is_config == 1) {
             $values[$daoResult->id]['action'] = CRM_Core_Action::formLink(self::customizeActionLinks(),
               NULL,
    -          array('id' => $daoResult->id),
    +          ['id' => $daoResult->id],
               ts('more'),
               FALSE,
               'persistent.config.actions',
    @@ -127,7 +127,7 @@ public function browse() {
           if ($daoResult->is_config == 0) {
             $values[$daoResult->id]['action'] = CRM_Core_Action::formLink(self::stringActionLinks(),
               NULL,
    -          array('id' => $daoResult->id),
    +          ['id' => $daoResult->id],
               ts('more'),
               FALSE,
               'persistent.row.actions',
    @@ -137,10 +137,10 @@ public function browse() {
             $configStrings[$daoResult->id] = $values[$daoResult->id];
           }
         }
    -    $rows = array(
    +    $rows = [
           'configTemplates' => $configStrings,
           'customizeTemplates' => $configCustomization,
    -    );
    +    ];
         $this->assign('rows', $rows);
       }
     
    diff --git a/CRM/Admin/Page/PreferencesDate.php b/CRM/Admin/Page/PreferencesDate.php
    index da39905bcaf5..053d7efb28c0 100644
    --- a/CRM/Admin/Page/PreferencesDate.php
    +++ b/CRM/Admin/Page/PreferencesDate.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/setting/preferences/date',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Date Type'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Admin/Page/RelationshipType.php b/CRM/Admin/Page/RelationshipType.php
    index 9a4f44941dde..8657da762576 100644
    --- a/CRM/Admin/Page/RelationshipType.php
    +++ b/CRM/Admin/Page/RelationshipType.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::VIEW => [
               'name' => ts('View'),
               'url' => 'civicrm/admin/reltype',
               'qs' => 'action=view&id=%%id%%&reset=1',
               'title' => ts('View Relationship Type'),
    -        ),
    -        CRM_Core_Action::UPDATE => array(
    +        ],
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/reltype',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Relationship Type'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Relationship Type'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Relationship Type'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/reltype',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Reletionship Type'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Admin/Page/ScheduleReminders.php b/CRM/Admin/Page/ScheduleReminders.php
    index 8148cadb63d1..c87d5c654ec8 100644
    --- a/CRM/Admin/Page/ScheduleReminders.php
    +++ b/CRM/Admin/Page/ScheduleReminders.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/scheduleReminders',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Schedule Reminders'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Label Format'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Label Format'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/scheduleReminders',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Schedule Reminders'),
    -        ),
    -      );
    +        ],
    +      ];
         }
     
         return self::$_links;
    @@ -153,7 +153,7 @@ public function browse($action = NULL) {
             $format['action'] = CRM_Core_Action::formLink(
               self::links(),
               $action,
    -          array('id' => $format['id']),
    +          ['id' => $format['id']],
               ts('more'),
               FALSE,
               'actionSchedule.manage.action',
    diff --git a/CRM/Admin/Page/Setting.php b/CRM/Admin/Page/Setting.php
    index af66540df01d..0f747b58c155 100644
    --- a/CRM/Admin/Page/Setting.php
    +++ b/CRM/Admin/Page/Setting.php
    @@ -1,9 +1,9 @@
     urlPath[3])) {
    +        $calls = CRM_Utils_Request::retrieve('calls', 'String', CRM_Core_DAO::$_nullObject, TRUE, NULL, 'POST', TRUE);
    +        $calls = json_decode($calls, TRUE);
    +        $response = [];
    +        foreach ($calls as $index => $call) {
    +          $response[$index] = call_user_func_array([$this, 'execute'], $call);
    +        }
    +      }
    +      // Call single
    +      else {
    +        $entity = $this->urlPath[3];
    +        $action = $this->urlPath[4];
    +        $params = CRM_Utils_Request::retrieve('params', 'String');
    +        $params = $params ? json_decode($params, TRUE) : [];
    +        $index = CRM_Utils_Request::retrieve('index', 'String');
    +        $response = $this->execute($entity, $action, $params, $index);
    +      }
    +    }
    +    catch (Exception $e) {
    +      http_response_code(500);
    +      $response = [
    +        'error_code' => $e->getCode(),
    +      ];
    +      if (CRM_Core_Permission::check('view debug output')) {
    +        $response['error_message'] = $e->getMessage();
    +        if (\Civi::settings()->get('backtrace')) {
    +          $response['backtrace'] = $e->getTrace();
    +        }
    +      }
    +    }
    +    CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
    +    echo json_encode($response);
    +    CRM_Utils_System::civiExit();
    +  }
    +
    +  /**
    +   * Run api call & prepare result for json encoding
    +   *
    +   * @param string $entity
    +   * @param string $action
    +   * @param array $params
    +   * @param string $index
    +   * @return array
    +   */
    +  protected function execute($entity, $action, $params = [], $index = NULL) {
    +    $params['checkPermissions'] = TRUE;
    +
    +    // Handle numeric indexes later so we can get the count
    +    $itemAt = CRM_Utils_Type::validate($index, 'Integer', FALSE);
    +
    +    $result = civicrm_api4($entity, $action, $params, isset($itemAt) ? NULL : $index);
    +
    +    // Convert arrayObject into something more suitable for json
    +    $vals = ['values' => isset($itemAt) ? $result->itemAt($itemAt) : (array) $result];
    +    foreach (get_class_vars(get_class($result)) as $key => $val) {
    +      $vals[$key] = $result->$key;
    +    }
    +    $vals['count'] = $result->count();
    +    return $vals;
    +  }
    +
    +}
    diff --git a/CRM/Api4/Page/Api4Explorer.php b/CRM/Api4/Page/Api4Explorer.php
    new file mode 100644
    index 000000000000..820392339ae8
    --- /dev/null
    +++ b/CRM/Api4/Page/Api4Explorer.php
    @@ -0,0 +1,62 @@
    + \CRM_Core_DAO::acceptedSQLOperators(),
    +      'basePath' => Civi::resources()->getUrl('civicrm'),
    +      'schema' => (array) \Civi\Api4\Entity::get()->setChain(['fields' => ['$name', 'getFields']])->execute(),
    +      'links' => (array) \Civi\Api4\Entity::getLinks()->execute(),
    +    ];
    +    Civi::resources()
    +      ->addVars('api4', $vars)
    +      ->addScriptFile('civicrm', 'js/load-bootstrap.js')
    +      ->addScriptFile('civicrm', 'bower_components/js-yaml/dist/js-yaml.min.js')
    +      ->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js')
    +      ->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css');
    +
    +    $loader = new Civi\Angular\AngularLoader();
    +    $loader->setModules(['api4Explorer']);
    +    $loader->setPageName('civicrm/api4');
    +    $loader->useApp([
    +      'defaultRoute' => '/explorer',
    +    ]);
    +    $loader->load();
    +    parent::run();
    +  }
    +
    +}
    diff --git a/CRM/Api4/Services.php b/CRM/Api4/Services.php
    new file mode 100644
    index 000000000000..03dcf6c5afc6
    --- /dev/null
    +++ b/CRM/Api4/Services.php
    @@ -0,0 +1,114 @@
    +load('Civi/Api4/services.xml');
    +
    +    self::loadServices('Civi\Api4\Service\Spec\Provider', 'spec_provider', $container);
    +    self::loadServices('Civi\Api4\Event\Subscriber', 'event_subscriber', $container);
    +
    +    $container->getDefinition('civi_api_kernel')->addMethodCall(
    +      'registerApiProvider',
    +      [new Reference('action_object_provider')]
    +    );
    +
    +    // add event subscribers$container->get(
    +    $dispatcher = $container->getDefinition('dispatcher');
    +    $subscribers = $container->findTaggedServiceIds('event_subscriber');
    +
    +    foreach (array_keys($subscribers) as $subscriber) {
    +      $dispatcher->addMethodCall(
    +        'addSubscriber',
    +        [new Reference($subscriber)]
    +      );
    +    }
    +
    +    // add spec providers
    +    $providers = $container->findTaggedServiceIds('spec_provider');
    +    $gatherer = $container->getDefinition('spec_gatherer');
    +
    +    foreach (array_keys($providers) as $provider) {
    +      $gatherer->addMethodCall(
    +        'addSpecProvider',
    +        [new Reference($provider)]
    +      );
    +    }
    +
    +    if (defined('CIVICRM_UF') && CIVICRM_UF === 'UnitTests') {
    +      $loader->load('tests/phpunit/api/v4/services.xml');
    +    }
    +  }
    +
    +  /**
    +   * Load all services in a given directory
    +   *
    +   * @param string $namespace
    +   * @param string $tag
    +   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    +   */
    +  public static function loadServices($namespace, $tag, $container) {
    +    $namespace = \CRM_Utils_File::addTrailingSlash($namespace, '\\');
    +    $locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')],
    +      array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath')
    +    );
    +    foreach ($locations as $location) {
    +      $path = \CRM_Utils_File::addTrailingSlash(dirname($location)) . str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
    +      $container->addResource(new \Symfony\Component\Config\Resource\DirectoryResource($path, ';\.php$;'));
    +      foreach (glob("$path*.php") as $file) {
    +        $matches = [];
    +        preg_match('/(\w*).php/', $file, $matches);
    +        $serviceName = $namespace . array_pop($matches);
    +        $serviceClass = new \ReflectionClass($serviceName);
    +        if ($serviceClass->isInstantiable()) {
    +          $definition = $container->register(str_replace('\\', '_', $serviceName), $serviceName);
    +          $definition->addTag($tag);
    +        }
    +      }
    +    }
    +  }
    +
    +}
    diff --git a/CRM/Badge/BAO/Badge.php b/CRM/Badge/BAO/Badge.php
    index 983de171ac9b..b4ff8e41030b 100644
    --- a/CRM/Badge/BAO/Badge.php
    +++ b/CRM/Badge/BAO/Badge.php
    @@ -1,7 +1,7 @@
     pdf->Output(CRM_Utils_String::munge($layoutInfo['title'], '_', 64) . '.pdf', 'D');
    -    CRM_Utils_System::civiExit(1);
    +    CRM_Utils_System::civiExit();
       }
     
       /**
    @@ -88,7 +88,7 @@ public function createLabels(&$participants, &$layoutInfo) {
        *   row with meta data
        */
       public static function formatLabel(&$row, &$layout) {
    -    $formattedRow = array('labelFormat' => $layout['label_format_name']);
    +    $formattedRow = ['labelFormat' => $layout['label_format_name']];
         $formattedRow['labelTitle'] = $layout['title'];
         $formattedRow['labelId'] = $layout['id'];
     
    @@ -103,14 +103,14 @@ public static function formatLabel(&$row, &$layout) {
               }
             }
     
    -        $formattedRow['token'][$key] = array(
    +        $formattedRow['token'][$key] = [
               'value' => $value,
               'font_name' => $layout['data']['font_name'][$key],
               'font_size' => $layout['data']['font_size'][$key],
               'font_style' => $layout['data']['font_style'][$key],
               'text_alignment' => $layout['data']['text_alignment'][$key],
               'token' => $layout['data']['token'][$key],
    -        );
    +        ];
           }
         }
     
    @@ -147,10 +147,10 @@ public static function formatLabel(&$row, &$layout) {
         }
     
         if (!empty($layout['data']['add_barcode'])) {
    -      $formattedRow['barcode'] = array(
    +      $formattedRow['barcode'] = [
             'alignment' => $layout['data']['barcode_alignment'],
             'type' => $layout['data']['barcode_type'],
    -      );
    +      ];
         }
     
         // finally assign all the row values, so that we can use it for barcode etc
    @@ -225,18 +225,18 @@ public function labelCreator(&$formattedRow, $cellspacing = 0) {
               break;
           }
           $this->pdf->Image($formattedRow['participant_image'], $x + $imageAlign, $y + $startOffset, CRM_Utils_Array::value('width_participant_image', $formattedRow), CRM_Utils_Array::value('height_participant_image', $formattedRow));
    -      if ($startOffset == NULL && CRM_Utils_Array::value('height_participant_image', $formattedRow)) {
    -        $startOffset = CRM_Utils_Array::value('height_participant_image', $formattedRow);
    +      if ($startOffset == NULL && !empty($formattedRow['height_participant_image'])) {
    +        $startOffset = $formattedRow['height_participant_image'];
           }
         }
     
    -    $this->pdf->SetLineStyle(array(
    +    $this->pdf->SetLineStyle([
           'width' => 0.1,
           'cap' => 'round',
           'join' => 'round',
           'dash' => '2,2',
    -      'color' => array(0, 0, 200),
    -    ));
    +      'color' => [0, 0, 200],
    +    ]);
     
         $rowCount = CRM_Badge_Form_Layout::FIELD_ROWCOUNT;
         for ($i = 1; $i <= $rowCount; $i++) {
    @@ -304,7 +304,7 @@ public function labelCreator(&$formattedRow, $cellspacing = 0) {
                 break;
             }
     
    -        $style = array(
    +        $style = [
               'position' => '',
               'align' => '',
               'stretch' => FALSE,
    @@ -313,13 +313,13 @@ public function labelCreator(&$formattedRow, $cellspacing = 0) {
               'border' => FALSE,
               'hpadding' => 13.5,
               'vpadding' => 'auto',
    -          'fgcolor' => array(0, 0, 0),
    +          'fgcolor' => [0, 0, 0],
               'bgcolor' => FALSE,
               'text' => FALSE,
               'font' => 'helvetica',
               'fontsize' => 8,
               'stretchtext' => 0,
    -        );
    +        ];
     
             $this->pdf->write1DBarcode($data['current_value'], 'C128', $xAlign, $y + $this->pdf->height - 10, '70',
               12, 0.4, $style, 'B');
    @@ -342,14 +342,14 @@ public function labelCreator(&$formattedRow, $cellspacing = 0) {
                 break;
             }
     
    -        $style = array(
    +        $style = [
               'border' => FALSE,
               'hpadding' => 13.5,
               'vpadding' => 'auto',
    -          'fgcolor' => array(0, 0, 0),
    +          'fgcolor' => [0, 0, 0],
               'bgcolor' => FALSE,
               'position' => '',
    -        );
    +        ];
     
             $this->pdf->write2DBarcode($data['current_value'], 'QRCODE,H', $xAlign, $y + $this->pdf->height - 26, 30,
               30, $style, 'B');
    @@ -400,7 +400,7 @@ public static function getImageProperties($img, $imgRes = 300, $w = NULL, $h = N
         $f = $imgRes / 25.4;
         $w = !empty($w) ? $w : $imgsize[0] / $f;
         $h = !empty($h) ? $h : $imgsize[1] / $f;
    -    return array($w, $h);
    +    return [$w, $h];
       }
     
       /**
    @@ -415,7 +415,7 @@ public static function buildBadges(&$params, &$form) {
         $layoutInfo = CRM_Badge_BAO_Layout::buildLayout($params);
     
         // split/get actual field names from token and individual contact image URLs
    -    $returnProperties = array();
    +    $returnProperties = [];
         if (!empty($layoutInfo['data']['token'])) {
           foreach ($layoutInfo['data']['token'] as $index => $value) {
             $element = '';
    @@ -445,7 +445,7 @@ public static function buildBadges(&$params, &$form) {
         }
     
         // add additional required fields for query execution
    -    $additionalFields = array('participant_register_date', 'participant_id', 'event_id', 'contact_id', 'image_URL');
    +    $additionalFields = ['participant_register_date', 'participant_id', 'event_id', 'contact_id', 'image_URL'];
         foreach ($additionalFields as $field) {
           $returnProperties[$field] = 1;
         }
    @@ -479,10 +479,10 @@ public static function buildBadges(&$params, &$form) {
         $queryString = "$select $from $where $having $sortOrder";
     
         $dao = CRM_Core_DAO::executeQuery($queryString);
    -    $rows = array();
    +    $rows = [];
         while ($dao->fetch()) {
           $query->convertToPseudoNames($dao);
    -      $rows[$dao->participant_id] = array();
    +      $rows[$dao->participant_id] = [];
           foreach ($returnProperties as $key => $dontCare) {
             $value = isset($dao->$key) ? $dao->$key : NULL;
             // Format custom fields
    diff --git a/CRM/Badge/BAO/Layout.php b/CRM/Badge/BAO/Layout.php
    index 40472b3c5445..8ad8ca1dad18 100644
    --- a/CRM/Badge/BAO/Layout.php
    +++ b/CRM/Badge/BAO/Layout.php
    @@ -1,9 +1,9 @@
     find();
     
    -    $labels = array();
    +    $labels = [];
         while ($printLabel->fetch()) {
           $labels[$printLabel->id] = $printLabel->title;
         }
    @@ -159,10 +158,11 @@ public static function getList() {
        *   array formatted array
        */
       public static function buildLayout(&$params) {
    -    $layoutParams = array('id' => $params['badge_id']);
    +    $layoutParams = ['id' => $params['badge_id']];
         CRM_Badge_BAO_Layout::retrieve($layoutParams, $layoutInfo);
     
    -    $formatProperties = CRM_Core_OptionGroup::getValue('name_badge', $layoutInfo['label_format_name'], 'name');
    +    $formatProperties = CRM_Core_PseudoConstant::getKey('CRM_Core_DAO_PrintLabel', 'label_format_name', $layoutInfo['label_format_name']);
    +
         $layoutInfo['format'] = json_decode($formatProperties, TRUE);
         $layoutInfo['data'] = CRM_Badge_BAO_Layout::getDecodedData($layoutInfo['data']);
         return $layoutInfo;
    @@ -177,7 +177,7 @@ public static function buildLayout(&$params) {
        * @return array
        *   associated array of decoded elements
        */
    -  static public function getDecodedData($jsonData) {
    +  public static function getDecodedData($jsonData) {
         return json_decode($jsonData, TRUE);
       }
     
    diff --git a/CRM/Badge/Form/Layout.php b/CRM/Badge/Form/Layout.php
    index 8ff71a23213b..af93d47ee057 100644
    --- a/CRM/Badge/Form/Layout.php
    +++ b/CRM/Badge/Form/Layout.php
    @@ -1,9 +1,9 @@
     addSetting(
    -      array(
    +      [
             'kcfinderPath' => $config->userFrameworkResourceURL . 'packages' . DIRECTORY_SEPARATOR,
    -      )
    +      ]
         );
         $resources->addScriptFile('civicrm', 'templates/CRM/Badge/Form/Layout.js', 1, 'html-header');
     
    @@ -60,25 +60,25 @@ public function buildQuickForm() {
         $this->add('text', 'title', ts('Title'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_PrintLabel', 'title'), TRUE);
     
         $labelStyle = CRM_Core_BAO_LabelFormat::getList(TRUE, 'name_badge');
    -    $this->add('select', 'label_format_name', ts('Label Format'), array('' => ts('- select -')) + $labelStyle, TRUE);
    +    $this->add('select', 'label_format_name', ts('Label Format'), ['' => ts('- select -')] + $labelStyle, TRUE);
     
         $this->add('text', 'description', ts('Description'),
           CRM_Core_DAO::getAttribute('CRM_Core_DAO_PrintLabel', 'title'));
     
         // get the tokens
         $contactTokens = CRM_Core_SelectValues::contactTokens();
    -    $eventTokens = array(
    +    $eventTokens = [
           '{event.event_id}' => ts('Event ID'),
           '{event.title}' => ts('Event Title'),
           '{event.start_date}' => ts('Event Start Date'),
           '{event.end_date}' => ts('Event End Date'),
    -    );
    +    ];
         $participantTokens = CRM_Core_SelectValues::participantTokens();
     
         $tokens = array_merge($contactTokens, $eventTokens, $participantTokens);
         asort($tokens);
     
    -    $tokens = array_merge(array('spacer' => ts('- spacer -')) + $tokens);
    +    $tokens = array_merge(['spacer' => ts('- spacer -')] + $tokens);
     
         $fontSizes = CRM_Core_BAO_LabelFormat::getFontSizes();
         $fontStyles = CRM_Core_BAO_LabelFormat::getFontStyles();
    @@ -89,7 +89,7 @@ public function buildQuickForm() {
     
         $rowCount = self::FIELD_ROWCOUNT;
         for ($i = 1; $i <= $rowCount; $i++) {
    -      $this->add('select', "token[$i]", ts('Token'), array('' => ts('- skip -')) + $tokens);
    +      $this->add('select', "token[$i]", ts('Token'), ['' => ts('- skip -')] + $tokens);
           $this->add('select', "font_name[$i]", ts('Font Name'), $fontNames);
           $this->add('select', "font_size[$i]", ts('Font Size'), $fontSizes);
           $this->add('select', "font_style[$i]", ts('Font Style'), $fontStyles);
    @@ -103,20 +103,20 @@ public function buildQuickForm() {
         $this->add('select', "barcode_type", ts('Type'), $barcodeTypes);
         $this->add('select', "barcode_alignment", ts('Alignment'), $textAlignment);
     
    -    $attributes = array('readonly' => TRUE);
    +    $attributes = ['readonly' => TRUE];
         $this->add('text', 'image_1', ts('Image (top left)'),
           $attributes + CRM_Core_DAO::getAttribute('CRM_Core_DAO_PrintLabel', 'title'));
    -    $this->add('text', 'width_image_1', ts('Width (mm)'), array('size' => 6));
    -    $this->add('text', 'height_image_1', ts('Height (mm)'), array('size' => 6));
    +    $this->add('text', 'width_image_1', ts('Width (mm)'), ['size' => 6]);
    +    $this->add('text', 'height_image_1', ts('Height (mm)'), ['size' => 6]);
     
         $this->add('text', 'image_2', ts('Image (top right)'),
           $attributes + CRM_Core_DAO::getAttribute('CRM_Core_DAO_PrintLabel', 'title'));
    -    $this->add('text', 'width_image_2', ts('Width (mm)'), array('size' => 6));
    -    $this->add('text', 'height_image_2', ts('Height (mm)'), array('size' => 6));
    +    $this->add('text', 'width_image_2', ts('Width (mm)'), ['size' => 6]);
    +    $this->add('text', 'height_image_2', ts('Height (mm)'), ['size' => 6]);
     
         $this->add('checkbox', 'show_participant_image', ts('Use Participant Image?'));
    -    $this->add('text', 'width_participant_image', ts('Width (mm)'), array('size' => 6));
    -    $this->add('text', 'height_participant_image', ts('Height (mm)'), array('size' => 6));
    +    $this->add('text', 'width_participant_image', ts('Width (mm)'), ['size' => 6]);
    +    $this->add('text', 'height_participant_image', ts('Height (mm)'), ['size' => 6]);
         $this->add('select', "alignment_participant_image", ts('Image Alignment'), $imageAlignment);
     
         $this->add('checkbox', 'is_default', ts('Default?'));
    @@ -130,22 +130,21 @@ public function buildQuickForm() {
         $this->addRule('height_participant_image', ts('Enter valid height'), 'positiveInteger');
         $this->addRule('width_participant_image', ts('Enter valid height'), 'positiveInteger');
     
    -    $this->addButtons(array(
    -        array(
    -          'type' => 'next',
    -          'name' => ts('Save'),
    -          'isDefault' => TRUE,
    -        ),
    -        array(
    -          'type' => 'refresh',
    -          'name' => ts('Save and Preview'),
    -        ),
    -        array(
    -          'type' => 'cancel',
    -          'name' => ts('Cancel'),
    -        ),
    -      )
    -    );
    +    $this->addButtons([
    +      [
    +        'type' => 'next',
    +        'name' => ts('Save'),
    +        'isDefault' => TRUE,
    +      ],
    +      [
    +        'type' => 'refresh',
    +        'name' => ts('Save and Preview'),
    +      ],
    +      [
    +        'type' => 'cancel',
    +        'name' => ts('Cancel'),
    +      ],
    +    ]);
       }
     
       /**
    @@ -154,7 +153,7 @@ public function buildQuickForm() {
       public function setDefaultValues() {
         if (isset($this->_id)) {
           $defaults = array_merge($this->_values,
    -        CRM_Badge_BAO_Layout::getDecodedData($this->_values['data']));
    +        CRM_Badge_BAO_Layout::getDecodedData(CRM_Utils_Array::value('data', $this->_values, '[]')));
         }
         else {
           for ($i = 1; $i <= self::FIELD_ROWCOUNT; $i++) {
    @@ -201,7 +200,7 @@ public function postProcess() {
         }
         else {
           CRM_Core_Session::setStatus(ts("The badge layout '%1' has been saved.",
    -        array(1 => $params['title'])
    +        [1 => $params['title']]
           ), ts('Saved'), 'success');
         }
       }
    @@ -220,7 +219,7 @@ public function buildPreview(&$params) {
         }
     
         $this->_single = TRUE;
    -    $this->_participantIds = array($participantID);
    +    $this->_participantIds = [$participantID];
         $this->_componentClause = " civicrm_participant.id = $participantID ";
     
         CRM_Badge_BAO_Badge::buildBadges($params, $this);
    diff --git a/CRM/Badge/Page/AJAX.php b/CRM/Badge/Page/AJAX.php
    index 986efcafdcf5..4e0af60ad66e 100644
    --- a/CRM/Badge/Page/AJAX.php
    +++ b/CRM/Badge/Page/AJAX.php
    @@ -1,9 +1,9 @@
      $w, 'height' => $h));
    +    CRM_Utils_JSON::output(['width' => $w, 'height' => $h]);
       }
     
     }
    diff --git a/CRM/Badge/Page/Layout.php b/CRM/Badge/Page/Layout.php
    index 9a448c53d221..da978c55f711 100644
    --- a/CRM/Badge/Page/Layout.php
    +++ b/CRM/Badge/Page/Layout.php
    @@ -1,9 +1,9 @@
      array(
    +      self::$_links = [
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/admin/badgelayout',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Badge Layout'),
    -        ),
    -        CRM_Core_Action::DISABLE => array(
    +        ],
    +        CRM_Core_Action::DISABLE => [
               'name' => ts('Disable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Disable Badge Layout'),
    -        ),
    -        CRM_Core_Action::ENABLE => array(
    +        ],
    +        CRM_Core_Action::ENABLE => [
               'name' => ts('Enable'),
               'ref' => 'crm-enable-disable',
               'title' => ts('Enable Badge Layout'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/admin/badgelayout',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Badge Layout'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return self::$_links;
       }
    diff --git a/CRM/Batch/BAO/Batch.php b/CRM/Batch/BAO/Batch.php
    index 541669a584e4..93bb4b6c3e9f 100644
    --- a/CRM/Batch/BAO/Batch.php
    +++ b/CRM/Batch/BAO/Batch.php
    @@ -1,9 +1,9 @@
     copyValues($params);
    -    if ($context == 'financialBatch' && !empty($ids['batchID'])) {
    -      $batch->id = $ids['batchID'];
    -    }
         $batch->save();
     
    +    CRM_Utils_Hook::post($op, 'Batch', $batch->id, $batch);
    +
         return $batch;
       }
     
    @@ -132,32 +132,7 @@ public static function getProfileId($batchTypeId) {
       public static function generateBatchName() {
         $sql = "SELECT max(id) FROM civicrm_batch";
         $batchNo = CRM_Core_DAO::singleValueQuery($sql) + 1;
    -    return ts('Batch %1', array(1 => $batchNo)) . ': ' . date('Y-m-d');
    -  }
    -
    -  /**
    -   * Create entity batch entry.
    -   *
    -   * @param array $params
    -   * @return array
    -   */
    -  public static function addBatchEntity(&$params) {
    -    $entityBatch = new CRM_Batch_DAO_EntityBatch();
    -    $entityBatch->copyValues($params);
    -    $entityBatch->save();
    -    return $entityBatch;
    -  }
    -
    -  /**
    -   * Remove entries from entity batch.
    -   * @param array $params
    -   * @return CRM_Batch_DAO_EntityBatch
    -   */
    -  public static function removeBatchEntity($params) {
    -    $entityBatch = new CRM_Batch_DAO_EntityBatch();
    -    $entityBatch->copyValues($params);
    -    $entityBatch->delete();
    -    return $entityBatch;
    +    return ts('Batch %1', [1 => $batchNo]) . ': ' . date('Y-m-d');
       }
     
       /**
    @@ -170,9 +145,11 @@ public static function removeBatchEntity($params) {
        */
       public static function deleteBatch($batchId) {
         // delete entry from batch table
    +    CRM_Utils_Hook::pre('delete', 'Batch', $batchId, CRM_Core_DAO::$_nullArray);
         $batch = new CRM_Batch_DAO_Batch();
         $batch->id = $batchId;
         $batch->delete();
    +    CRM_Utils_Hook::post('delete', 'Batch', $batch->id, $batch);
         return TRUE;
       }
     
    @@ -185,7 +162,7 @@ public static function deleteBatch($batchId) {
        * @return array
        *   associated array of batch list
        */
    -  public function getBatchListSelector(&$params) {
    +  public static function getBatchListSelector(&$params) {
         // format the params
         $params['offset'] = ($params['page'] - 1) * $params['rp'];
         $params['rowCount'] = $params['rp'];
    @@ -195,12 +172,12 @@ public function getBatchListSelector(&$params) {
         $batches = self::getBatchList($params);
     
         // get batch totals for open batches
    -    $fetchTotals = array();
    -    $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', array('labelColumn' => 'name'));
    -    $batchStatus = array(
    +    $fetchTotals = [];
    +    $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', ['labelColumn' => 'name']);
    +    $batchStatus = [
           array_search('Open', $batchStatus),
           array_search('Reopened', $batchStatus),
    -    );
    +    ];
         if ($params['context'] == 'financialBatch') {
           foreach ($batches as $id => $batch) {
             if (in_array($batch['status_id'], $batchStatus)) {
    @@ -214,10 +191,10 @@ public function getBatchListSelector(&$params) {
         $params['total'] = self::getBatchCount($params);
     
         // format params and add links
    -    $batchList = array();
    +    $batchList = [];
     
         foreach ($batches as $id => $value) {
    -      $batch = array();
    +      $batch = [];
           if ($params['context'] == 'financialBatch') {
             $batch['check'] = $value['check'];
           }
    @@ -225,9 +202,10 @@ public function getBatchListSelector(&$params) {
           $batch['total'] = '';
           $batch['payment_instrument'] = $value['payment_instrument'];
           $batch['item_count'] = CRM_Utils_Array::value('item_count', $value);
    -      $batch['type'] = $value['batch_type'];
    +      $batch['type'] = CRM_Utils_Array::value('batch_type', $value);
           if (!empty($value['total'])) {
    -        $batch['total'] = CRM_Utils_Money::format($value['total']);
    +        // CRM-21205
    +        $batch['total'] = CRM_Utils_Money::format($value['total'], $value['currency']);
           }
     
           // Compare totals with actuals
    @@ -252,60 +230,70 @@ public function getBatchListSelector(&$params) {
        * @return array
        */
       public static function getBatchList(&$params) {
    -    $whereClause = self::whereClause($params);
    +    $apiParams = self::whereClause($params);
     
         if (!empty($params['rowCount']) && is_numeric($params['rowCount'])
           && is_numeric($params['offset']) && $params['rowCount'] > 0
         ) {
    -      $limit = " LIMIT {$params['offset']}, {$params['rowCount']} ";
    +      $apiParams['options'] = ['offset' => $params['offset'], 'limit' => $params['rowCount']];
         }
    -
    -    $orderBy = ' ORDER BY batch.id desc';
    +    $apiParams['options']['sort'] = 'id DESC';
         if (!empty($params['sort'])) {
    -      $orderBy = ' ORDER BY ' . CRM_Utils_Type::escape($params['sort'], 'String');
    -    }
    -
    -    $query = "
    -      SELECT batch.*, c.sort_name created_by
    -      FROM  civicrm_batch batch
    -      INNER JOIN civicrm_contact c ON batch.created_id = c.id
    -    WHERE {$whereClause}
    -    {$orderBy}
    -    {$limit}";
    -
    -    $object = CRM_Core_DAO::executeQuery($query, $params, TRUE, 'CRM_Batch_DAO_Batch');
    +      $apiParams['options']['sort'] = CRM_Utils_Type::escape($params['sort'], 'String');
    +    }
    +
    +    $return = [
    +      "id",
    +      "name",
    +      "title",
    +      "description",
    +      "created_date",
    +      "status_id",
    +      "modified_id",
    +      "modified_date",
    +      "type_id",
    +      "mode_id",
    +      "total",
    +      "item_count",
    +      "exported_date",
    +      "payment_instrument_id",
    +      "created_id.sort_name",
    +      "created_id",
    +    ];
    +    $apiParams['return'] = $return;
    +    $batches = civicrm_api3('Batch', 'get', $apiParams);
    +    $obj = new CRM_Batch_BAO_Batch();
         if (!empty($params['context'])) {
    -      $links = self::links($params['context']);
    +      $links = $obj->links($params['context']);
         }
         else {
    -      $links = self::links();
    +      $links = $obj->links();
         }
     
         $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id');
         $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id');
    -    $batchStatusByName = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', array('labelColumn' => 'name'));
    +    $batchStatusByName = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', ['labelColumn' => 'name']);
         $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
     
    -    $results = array();
    -    while ($object->fetch()) {
    -      $values = array();
    +    $results = [];
    +    foreach ($batches['values'] as $values) {
           $newLinks = $links;
    -      CRM_Core_DAO::storeValues($object, $values);
           $action = array_sum(array_keys($newLinks));
     
           if ($values['status_id'] == array_search('Closed', $batchStatusByName) && $params['context'] != 'financialBatch') {
    -        $newLinks = array();
    +        $newLinks = [];
           }
           elseif ($params['context'] == 'financialBatch') {
             $values['check'] = "";
     
             switch ($batchStatusByName[$values['status_id']]) {
               case 'Open':
    +          case 'Reopened':
                 CRM_Utils_Array::remove($newLinks, 'reopen', 'download');
                 break;
     
    @@ -316,23 +304,44 @@ public static function getBatchList(&$params) {
               case 'Exported':
                 CRM_Utils_Array::remove($newLinks, 'close', 'edit', 'reopen', 'export');
             }
    +        if (!CRM_Batch_BAO_Batch::checkBatchPermission('edit', $values['created_id'])) {
    +          CRM_Utils_Array::remove($newLinks, 'edit');
    +        }
    +        if (!CRM_Batch_BAO_Batch::checkBatchPermission('close', $values['created_id'])) {
    +          CRM_Utils_Array::remove($newLinks, 'close', 'export');
    +        }
    +        if (!CRM_Batch_BAO_Batch::checkBatchPermission('reopen', $values['created_id'])) {
    +          CRM_Utils_Array::remove($newLinks, 'reopen');
    +        }
    +        if (!CRM_Batch_BAO_Batch::checkBatchPermission('export', $values['created_id'])) {
    +          CRM_Utils_Array::remove($newLinks, 'export', 'download');
    +        }
    +        if (!CRM_Batch_BAO_Batch::checkBatchPermission('delete', $values['created_id'])) {
    +          CRM_Utils_Array::remove($newLinks, 'delete');
    +        }
           }
           if (!empty($values['type_id'])) {
             $values['batch_type'] = $batchTypes[$values['type_id']];
           }
           $values['batch_status'] = $batchStatus[$values['status_id']];
    -      $values['created_by'] = $object->created_by;
    +      $values['created_by'] = $values['created_id.sort_name'];
           $values['payment_instrument'] = '';
    -      if (!empty($object->payment_instrument_id)) {
    -        $values['payment_instrument'] = $paymentInstrument[$object->payment_instrument_id];
    +      if (!empty($values['payment_instrument_id'])) {
    +        $values['payment_instrument'] = $paymentInstrument[$values['payment_instrument_id']];
           }
    -      $tokens = array('id' => $object->id, 'status' => $values['status_id']);
    +      $tokens = ['id' => $values['id'], 'status' => $values['status_id']];
           if ($values['status_id'] == array_search('Exported', $batchStatusByName)) {
    -        $aid = CRM_Core_OptionGroup::getValue('activity_type', 'Export Accounting Batch');
    -        $activityParams = array('source_record_id' => $object->id, 'activity_type_id' => $aid);
    +        $aid = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Export Accounting Batch');
    +        $activityParams = ['source_record_id' => $values['id'], 'activity_type_id' => $aid];
             $exportActivity = CRM_Activity_BAO_Activity::retrieve($activityParams, $val);
    -        $fid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile', $exportActivity->id, 'file_id', 'entity_id');
    -        $tokens = array_merge(array('eid' => $exportActivity->id, 'fid' => $fid), $tokens);
    +        if ($exportActivity) {
    +          $fid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile', $exportActivity->id, 'file_id', 'entity_id');
    +          $fileHash = CRM_Core_BAO_File::generateFileHash($exportActivity->id, $fid);
    +          $tokens = array_merge(['eid' => $exportActivity->id, 'fid' => $fid, 'fcs' => $fileHash], $tokens);
    +        }
    +        else {
    +          CRM_Utils_Array::remove($newLinks, 'export', 'download');
    +        }
           }
           $values['action'] = CRM_Core_Action::formLink(
             $newLinks,
    @@ -342,9 +351,20 @@ public static function getBatchList(&$params) {
             FALSE,
             'batch.selector.row',
             'Batch',
    -        $object->id
    +        $values['id']
           );
    -      $results[$object->id] = $values;
    +      // CRM-21205
    +      $values['currency'] = CRM_Core_DAO::singleValueQuery("
    +        SELECT GROUP_CONCAT(DISTINCT ft.currency)
    +        FROM  civicrm_batch batch
    +        JOIN civicrm_entity_batch eb
    +          ON batch.id = eb.batch_id
    +        JOIN civicrm_financial_trxn ft
    +          ON eb.entity_id = ft.id
    +        WHERE batch.id = %1
    +        GROUP BY batch.id
    +      ", [1 => [$values['id'], 'Positive']]);
    +      $results[$values['id']] = $values;
         }
     
         return $results;
    @@ -359,12 +379,8 @@ public static function getBatchList(&$params) {
        * @return null|string
        */
       public static function getBatchCount(&$params) {
    -    $args = array();
    -    $whereClause = self::whereClause($params, $args);
    -    $query = " SELECT COUNT(*) FROM civicrm_batch batch
    -      INNER JOIN civicrm_contact c ON batch.created_id = c.id
    -      WHERE {$whereClause}";
    -    return CRM_Core_DAO::singleValueQuery($query);
    +    $apiParams = self::whereClause($params);
    +    return civicrm_api3('Batch', 'getCount', $apiParams);
       }
     
       /**
    @@ -376,43 +392,56 @@ public static function getBatchCount(&$params) {
        * @return string
        */
       public static function whereClause($params) {
    -    $clauses = array();
    +    $clauses = [];
         // Exclude data-entry batches
    -    $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', array('labelColumn' => 'name'));
    +    $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id', ['labelColumn' => 'name']);
         if (empty($params['status_id'])) {
    -      $clauses[] = 'batch.status_id <> ' . array_search('Data Entry', $batchStatus);
    +      $clauses['status_id'] = ['NOT IN' => ["Data Entry"]];
    +    }
    +
    +    $return = [
    +      "id",
    +      "name",
    +      "title",
    +      "description",
    +      "created_date",
    +      "status_id",
    +      "modified_id",
    +      "modified_date",
    +      "type_id",
    +      "mode_id",
    +      "total",
    +      "item_count",
    +      "exported_date",
    +      "payment_instrument_id",
    +      "created_id.sort_name",
    +      "created_id",
    +    ];
    +    if (!CRM_Core_Permission::check("view all manual batches")) {
    +      if (CRM_Core_Permission::check("view own manual batches")) {
    +        $loggedInContactId = CRM_Core_Session::singleton()->get('userID');
    +        $params['created_id'] = $loggedInContactId;
    +      }
    +      else {
    +        $params['created_id'] = 0;
    +      }
         }
    -
    -    $fields = array(
    -      'title' => 'String',
    -      'sort_name' => 'String',
    -      'status_id' => 'Integer',
    -      'payment_instrument_id' => 'Integer',
    -      'item_count' => 'Integer',
    -      'total' => 'Float',
    -    );
    -
    -    foreach ($fields as $field => $type) {
    -      $table = $field == 'sort_name' ? 'c' : 'batch';
    -      if (isset($params[$field])) {
    -        $value = CRM_Utils_Type::escape($params[$field], $type, FALSE);
    -        if ($value && $type == 'String') {
    -          $clauses[] = "$table.$field LIKE '%$value%'";
    -        }
    -        elseif ($value && $type == 'Float') {
    -          $clauses[] = "$table.$field = '$value'";
    -        }
    -        elseif ($value) {
    -          if ($field == 'status_id' && $value == array_search('Open', $batchStatus)) {
    -            $clauses[] = "$table.$field IN ($value," . array_search('Reopened', $batchStatus) . ')';
    -          }
    -          else {
    -            $clauses[] = "$table.$field = $value";
    -          }
    -        }
    +    foreach ($return as $field) {
    +      if (!isset($params[$field])) {
    +        continue;
    +      }
    +      $value = CRM_Utils_Type::escape($params[$field], 'String', FALSE);
    +      if (in_array($field, ['name', 'title', 'description', 'created_id.sort_name'])) {
    +        $clauses[$field] = ['LIKE' => "%{$value}%"];
    +      }
    +      elseif ($field == 'status_id' && $value == array_search('Open', $batchStatus)) {
    +        $clauses['status_id'] = ['IN' => ["Open", 'Reopened']];
    +      }
    +      else {
    +        $clauses[$field] = $value;
           }
         }
    -    return $clauses ? implode(' AND ', $clauses) : '1';
    +    return $clauses;
       }
     
       /**
    @@ -425,72 +454,72 @@ public static function whereClause($params) {
        */
       public function links($context = NULL) {
         if ($context == 'financialBatch') {
    -      $links = array(
    -        'transaction' => array(
    +      $links = [
    +        'transaction' => [
               'name' => ts('Transactions'),
               'url' => 'civicrm/batchtransaction',
               'qs' => 'reset=1&bid=%%id%%',
               'title' => ts('View/Add Transactions to Batch'),
    -        ),
    -        'edit' => array(
    +        ],
    +        'edit' => [
               'name' => ts('Edit'),
               'url' => 'civicrm/financial/batch',
               'qs' => 'reset=1&action=update&id=%%id%%&context=1',
               'title' => ts('Edit Batch'),
    -        ),
    -        'close' => array(
    +        ],
    +        'close' => [
               'name' => ts('Close'),
               'title' => ts('Close Batch'),
               'url' => '#',
               'extra' => 'rel="close"',
    -        ),
    -        'export' => array(
    +        ],
    +        'export' => [
               'name' => ts('Export'),
               'title' => ts('Export Batch'),
               'url' => '#',
               'extra' => 'rel="export"',
    -        ),
    -        'reopen' => array(
    +        ],
    +        'reopen' => [
               'name' => ts('Re-open'),
               'title' => ts('Re-open Batch'),
               'url' => '#',
               'extra' => 'rel="reopen"',
    -        ),
    -        'delete' => array(
    +        ],
    +        'delete' => [
               'name' => ts('Delete'),
               'title' => ts('Delete Batch'),
               'url' => '#',
               'extra' => 'rel="delete"',
    -        ),
    -        'download' => array(
    +        ],
    +        'download' => [
               'name' => ts('Download'),
               'url' => 'civicrm/file',
    -          'qs' => 'reset=1&id=%%fid%%&eid=%%eid%%',
    +          'qs' => 'reset=1&id=%%fid%%&eid=%%eid%%&fcs=%%fcs%%',
               'title' => ts('Download Batch'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         else {
    -      $links = array(
    -        CRM_Core_Action::COPY => array(
    +      $links = [
    +        CRM_Core_Action::COPY => [
               'name' => ts('Enter records'),
               'url' => 'civicrm/batch/entry',
               'qs' => 'id=%%id%%&reset=1',
               'title' => ts('Batch Data Entry'),
    -        ),
    -        CRM_Core_Action::UPDATE => array(
    +        ],
    +        CRM_Core_Action::UPDATE => [
               'name' => ts('Edit'),
               'url' => 'civicrm/batch',
               'qs' => 'action=update&id=%%id%%&reset=1',
               'title' => ts('Edit Batch'),
    -        ),
    -        CRM_Core_Action::DELETE => array(
    +        ],
    +        CRM_Core_Action::DELETE => [
               'name' => ts('Delete'),
               'url' => 'civicrm/batch',
               'qs' => 'action=delete&id=%%id%%',
               'title' => ts('Delete Batch'),
    -        ),
    -      );
    +        ],
    +      ];
         }
         return $links;
       }
    @@ -509,7 +538,7 @@ public static function getBatches() {
           AND status_id != {$dataEntryStatusId}
           ORDER BY title";
     
    -    $batches = array();
    +    $batches = [];
         $dao = CRM_Core_DAO::executeQuery($query);
         while ($dao->fetch()) {
           $batches[$dao->id] = $dao->title;
    @@ -517,7 +546,6 @@ public static function getBatches() {
         return $batches;
       }
     
    -
       /**
        * Calculate sum of all entries in a batch.
        * Used to validate and update item_count and total when closing an accounting batch
    @@ -526,7 +554,7 @@ public static function getBatches() {
        * @return array
        */
       public static function batchTotals($batchIds) {
    -    $totals = array_fill_keys($batchIds, array('item_count' => 0, 'total' => 0));
    +    $totals = array_fill_keys($batchIds, ['item_count' => 0, 'total' => 0]);
         if ($batchIds) {
           $sql = "SELECT eb.batch_id, COUNT(tx.id) AS item_count, SUM(tx.total_amount) AS total
           FROM civicrm_entity_batch eb
    @@ -537,7 +565,6 @@ public static function batchTotals($batchIds) {
           while ($dao->fetch()) {
             $totals[$dao->batch_id] = (array) $dao;
           }
    -      $dao->free();
         }
         return $totals;
       }
    @@ -573,8 +600,10 @@ public static function displayTotals($actual, $expected) {
        *   Associated array of batch ids.
        * @param string $exportFormat
        *   Export format.
    +   * @param bool $downloadFile
    +   *   Download export file?.
        */
    -  public static function exportFinancialBatch($batchIds, $exportFormat) {
    +  public static function exportFinancialBatch($batchIds, $exportFormat, $downloadFile) {
         if (empty($batchIds)) {
           CRM_Core_Error::fatal(ts('No batches were selected.'));
           return;
    @@ -593,17 +622,29 @@ public static function exportFinancialBatch($batchIds, $exportFormat) {
         else {
           CRM_Core_Error::fatal("Could not locate exporter: $exporterClass");
         }
    +    $export = [];
    +    $exporter->_isDownloadFile = $downloadFile;
         foreach ($batchIds as $batchId) {
    +      // export only batches whose status is set to Exported.
    +      $result = civicrm_api3('Batch', 'getcount', [
    +        'id' => $batchId,
    +        'status_id' => "Exported",
    +      ]);
    +      if (!$result) {
    +        continue;
    +      }
           $export[$batchId] = $exporter->generateExportQuery($batchId);
         }
    -    $exporter->makeExport($export);
    +    if ($export) {
    +      $exporter->makeExport($export);
    +    }
       }
     
       /**
        * @param array $batchIds
        * @param $status
        */
    -  public static function closeReOpen($batchIds = array(), $status) {
    +  public static function closeReOpen($batchIds = [], $status) {
         $batchStatus = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'status_id');
         $params['status_id'] = CRM_Utils_Array::key($status, $batchStatus);
         $session = CRM_Core_Session::singleton();
    @@ -658,11 +699,11 @@ public static function getBatchFinancialItems($entityID, $returnValues, $notPres
     LEFT JOIN civicrm_contribution_soft ON civicrm_contribution_soft.contribution_id = civicrm_contribution.id
     ";
     
    -    $searchFields = array(
    +    $searchFields = [
           'sort_name',
           'financial_type_id',
           'contribution_page_id',
    -      'payment_instrument_id',
    +      'contribution_payment_instrument_id',
           'contribution_trxn_id',
           'contribution_source',
           'contribution_currency_type',
    @@ -673,19 +714,20 @@ public static function getBatchFinancialItems($entityID, $returnValues, $notPres
           'contribution_receipt_date_is_not_null',
           'contribution_pcp_made_through_id',
           'contribution_pcp_display_in_roll',
    -      'contribution_date_relative',
           'contribution_amount_low',
           'contribution_amount_high',
           'contribution_in_honor_of',
           'contact_tags',
           'group',
    -      'contribution_date_relative',
    -      'contribution_date_high',
    -      'contribution_date_low',
    +      'receive_date_relative',
    +      'receive_date_high',
    +      'receive_date_low',
           'contribution_check_number',
           'contribution_status_id',
    -    );
    -    $values = array();
    +      'financial_trxn_card_type_id',
    +      'financial_trxn_pan_truncation',
    +    ];
    +    $values = [];
         foreach ($searchFields as $field) {
           if (isset($params[$field])) {
             $values[$field] = $params[$field];
    @@ -702,25 +744,37 @@ public static function getBatchFinancialItems($entityID, $returnValues, $notPres
             if ($field == 'group') {
               $from .= " LEFT JOIN civicrm_group_contact `civicrm_group_contact-{$params[$field]}` ON contact_a.id = `civicrm_group_contact-{$params[$field]}`.contact_id ";
             }
    -        if ($field == 'contribution_date_relative') {
    +        if ($field == 'receive_date_relative') {
               $relativeDate = explode('.', $params[$field]);
               $date = CRM_Utils_Date::relativeToAbsolute($relativeDate[0], $relativeDate[1]);
    -          $values['contribution_date_low'] = $date['from'];
    -          $values['contribution_date_high'] = $date['to'];
    -        }
    -        $searchParams = CRM_Contact_BAO_Query::convertFormValues($values);
    -        // @todo the use of defaultReturnProperties means the search will be inefficient
    -        // as slow-unneeded properties are included.
    -        $query = new CRM_Contact_BAO_Query($searchParams,
    -          CRM_Contribute_BAO_Query::defaultReturnProperties(CRM_Contact_BAO_Query::MODE_CONTRIBUTE,
    -            FALSE
    -          ), NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTRIBUTE
    -        );
    -        if ($field == 'contribution_date_high' || $field == 'contribution_date_low') {
    -          $query->dateQueryBuilder($params[$field], 'civicrm_contribution', 'contribution_date', 'receive_date', 'Contribution Date');
    +          $values['receive_date_low'] = $date['from'];
    +          $values['receive_date_high'] = $date['to'];
             }
           }
         }
    +
    +    $searchParams = CRM_Contact_BAO_Query::convertFormValues(
    +      $values,
    +      0,
    +      FALSE,
    +      NULL,
    +      [
    +        'financial_type_id',
    +        'contribution_soft_credit_type_id',
    +        'contribution_status_id',
    +        'contribution_page_id',
    +        'financial_trxn_card_type_id',
    +        'contribution_payment_instrument_id',
    +      ]
    +    );
    +    // @todo the use of defaultReturnProperties means the search will be inefficient
    +    // as slow-unneeded properties are included.
    +    $query = new CRM_Contact_BAO_Query($searchParams,
    +      CRM_Contribute_BAO_Query::defaultReturnProperties(CRM_Contact_BAO_Query::MODE_CONTRIBUTE,
    +        FALSE
    +      ), NULL, FALSE, FALSE, CRM_Contact_BAO_Query::MODE_CONTRIBUTE
    +    );
    +
         if (!empty($query->_where[0])) {
           $where = implode(' AND ', $query->_where[0]) .
             " AND civicrm_entity_batch.batch_id IS NULL ";
    @@ -762,7 +816,7 @@ public static function getBatchNames($batchIds) {
           FROM civicrm_batch
           WHERE id IN (' . $batchIds . ')';
     
    -    $batches = array();
    +    $batches = [];
         $dao = CRM_Core_DAO::executeQuery($query);
         while ($dao->fetch()) {
           $batches[$dao->id] = $dao->title;
    @@ -783,7 +837,7 @@ public static function getBatchStatuses($batchIds) {
           FROM civicrm_batch
           WHERE id IN (' . $batchIds . ')';
     
    -    $batches = array();
    +    $batches = [];
         $dao = CRM_Core_DAO::executeQuery($query);
         while ($dao->fetch()) {
           $batches[$dao->id] = $dao->status_id;
    @@ -791,4 +845,29 @@ public static function getBatchStatuses($batchIds) {
         return $batches;
       }
     
    +  /**
    +   * Function to check permission for batch.
    +   *
    +   * @param string $action
    +   * @param int $batchCreatedId
    +   *   batch created by contact id
    +   *
    +   * @return bool
    +   */
    +  public static function checkBatchPermission($action, $batchCreatedId = NULL) {
    +    if (CRM_Core_Permission::check("{$action} all manual batches")) {
    +      return TRUE;
    +    }
    +    if (CRM_Core_Permission::check("{$action} own manual batches")) {
    +      $loggedInContactId = CRM_Core_Session::singleton()->get('userID');
    +      if ($batchCreatedId == $loggedInContactId) {
    +        return TRUE;
    +      }
    +      elseif (CRM_Utils_System::isNull($batchCreatedId)) {
    +        return TRUE;
    +      }
    +    }
    +    return FALSE;
    +  }
    +
     }
    diff --git a/CRM/Batch/BAO/EntityBatch.php b/CRM/Batch/BAO/EntityBatch.php
    index fcb4a2c22cfd..426ce282622d 100644
    --- a/CRM/Batch/BAO/EntityBatch.php
    +++ b/CRM/Batch/BAO/EntityBatch.php
    @@ -1,9 +1,9 @@
     copyValues($params);
    +    $entityBatch->save();
    +    CRM_Utils_Hook::post($op, 'EntityBatch', $entityBatch->id, $entityBatch);
    +    return $entityBatch;
    +  }
    +
    +  /**
    +   * Remove entries from entity batch.
    +   * @param array|int $params
    +   * @return CRM_Batch_DAO_EntityBatch
    +   */
    +  public static function del($params) {
    +    if (!is_array($params)) {
    +      $params = ['id' => $params];
    +    }
    +    $entityBatch = new CRM_Batch_DAO_EntityBatch();
    +    $entityId = CRM_Utils_Array::value('id', $params);
    +    CRM_Utils_Hook::pre('delete', 'EntityBatch', $entityId, $params);
    +    $entityBatch->copyValues($params);
    +    $entityBatch->delete();
    +    CRM_Utils_Hook::post('delete', 'EntityBatch', $entityBatch->id, $entityBatch);
    +    return $entityBatch;
    +  }
    +
     }
    diff --git a/CRM/Batch/DAO/Batch.php b/CRM/Batch/DAO/Batch.php
    index 9adce55180f0..221cbef7af3c 100644
    --- a/CRM/Batch/DAO/Batch.php
    +++ b/CRM/Batch/DAO/Batch.php
    @@ -1,453 +1,470 @@
     __table = 'civicrm_batch';
         parent::__construct();
       }
    +
       /**
        * Returns foreign keys and entity references.
        *
        * @return array
        *   [CRM_Core_Reference_Interface]
        */
    -  static function getReferenceColumns() {
    +  public static function getReferenceColumns() {
         if (!isset(Civi::$statics[__CLASS__]['links'])) {
    -      Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__);
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'created_id', 'civicrm_contact', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'modified_id', 'civicrm_contact', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'saved_search_id', 'civicrm_saved_search', 'id');
    +      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'created_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'modified_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'saved_search_id', 'civicrm_saved_search', 'id');
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
         }
         return Civi::$statics[__CLASS__]['links'];
       }
    +
       /**
        * Returns all the column names of this table
        *
        * @return array
        */
    -  static function &fields() {
    +  public static function &fields() {
         if (!isset(Civi::$statics[__CLASS__]['fields'])) {
    -      Civi::$statics[__CLASS__]['fields'] = array(
    -        'id' => array(
    +      Civi::$statics[__CLASS__]['fields'] = [
    +        'id' => [
               'name' => 'id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch ID') ,
    -          'description' => 'Unique Address ID',
    -          'required' => true,
    +          'title' => ts('Batch ID'),
    +          'description' => ts('Unique Address ID'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_batch.id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -        ) ,
    -        'name' => array(
    +        ],
    +        'name' => [
               'name' => 'name',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Batch Name') ,
    -          'description' => 'Variable name/programmatic handle for this batch.',
    +          'title' => ts('Batch Name'),
    +          'description' => ts('Variable name/programmatic handle for this batch.'),
               'maxlength' => 64,
               'size' => CRM_Utils_Type::BIG,
    +          'where' => 'civicrm_batch.name',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'title' => array(
    +          ],
    +        ],
    +        'title' => [
               'name' => 'title',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Batch Title') ,
    -          'description' => 'Friendly Name.',
    +          'title' => ts('Batch Title'),
    +          'description' => ts('Friendly Name.'),
               'maxlength' => 255,
               'size' => CRM_Utils_Type::HUGE,
    +          'where' => 'civicrm_batch.title',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 1,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'description' => array(
    +          ],
    +        ],
    +        'description' => [
               'name' => 'description',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Batch Description') ,
    -          'description' => 'Description of this batch set.',
    +          'title' => ts('Batch Description'),
    +          'description' => ts('Description of this batch set.'),
               'rows' => 4,
               'cols' => 80,
    +          'where' => 'civicrm_batch.description',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 1,
    -          'html' => array(
    +          'html' => [
                 'type' => 'TextArea',
    -          ) ,
    -        ) ,
    -        'created_id' => array(
    +          ],
    +        ],
    +        'created_id' => [
               'name' => 'created_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Created By') ,
    -          'description' => 'FK to Contact ID',
    +          'title' => ts('Batch Created By'),
    +          'description' => ts('FK to Contact ID'),
    +          'where' => 'civicrm_batch.created_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'created_date' => array(
    +        ],
    +        'created_date' => [
               'name' => 'created_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Batch Created Date') ,
    -          'description' => 'When was this item created',
    +          'title' => ts('Batch Created Date'),
    +          'description' => ts('When was this item created'),
    +          'where' => 'civicrm_batch.created_date',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select Date',
    -          ) ,
    -        ) ,
    -        'modified_id' => array(
    +          ],
    +        ],
    +        'modified_id' => [
               'name' => 'modified_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Modified By') ,
    -          'description' => 'FK to Contact ID',
    +          'title' => ts('Batch Modified By'),
    +          'description' => ts('FK to Contact ID'),
    +          'where' => 'civicrm_batch.modified_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'modified_date' => array(
    +        ],
    +        'modified_date' => [
               'name' => 'modified_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Batch Modified Date') ,
    -          'description' => 'When was this item created',
    +          'title' => ts('Batch Modified Date'),
    +          'description' => ts('When was this item created'),
    +          'where' => 'civicrm_batch.modified_date',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -        ) ,
    -        'saved_search_id' => array(
    +        ],
    +        'saved_search_id' => [
               'name' => 'saved_search_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Smart Group') ,
    -          'description' => 'FK to Saved Search ID',
    +          'title' => ts('Batch Smart Group'),
    +          'description' => ts('FK to Saved Search ID'),
    +          'where' => 'civicrm_batch.saved_search_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_SavedSearch',
    -          'html' => array(
    +          'html' => [
                 'type' => 'EntityRef',
    -          ) ,
    -        ) ,
    -        'status_id' => array(
    +          ],
    +        ],
    +        'status_id' => [
               'name' => 'status_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Status') ,
    -          'description' => 'fk to Batch Status options in civicrm_option_values',
    -          'required' => true,
    +          'title' => ts('Batch Status'),
    +          'description' => ts('fk to Batch Status options in civicrm_option_values'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_batch.status_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'batch_status',
                 'optionEditPath' => 'civicrm/admin/options/batch_status',
    -          )
    -        ) ,
    -        'type_id' => array(
    +          ],
    +        ],
    +        'type_id' => [
               'name' => 'type_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Type') ,
    -          'description' => 'fk to Batch Type options in civicrm_option_values',
    +          'title' => ts('Batch Type'),
    +          'description' => ts('fk to Batch Type options in civicrm_option_values'),
    +          'where' => 'civicrm_batch.type_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'batch_type',
                 'optionEditPath' => 'civicrm/admin/options/batch_type',
    -          )
    -        ) ,
    -        'mode_id' => array(
    +          ],
    +        ],
    +        'mode_id' => [
               'name' => 'mode_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Mode') ,
    -          'description' => 'fk to Batch mode options in civicrm_option_values',
    +          'title' => ts('Batch Mode'),
    +          'description' => ts('fk to Batch mode options in civicrm_option_values'),
    +          'where' => 'civicrm_batch.mode_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'batch_mode',
                 'optionEditPath' => 'civicrm/admin/options/batch_mode',
    -          )
    -        ) ,
    -        'total' => array(
    +          ],
    +        ],
    +        'total' => [
               'name' => 'total',
               'type' => CRM_Utils_Type::T_MONEY,
    -          'title' => ts('Batch Total') ,
    -          'description' => 'Total amount for this batch.',
    -          'precision' => array(
    +          'title' => ts('Batch Total'),
    +          'description' => ts('Total amount for this batch.'),
    +          'precision' => [
                 20,
    -            2
    -          ) ,
    +            2,
    +          ],
    +          'where' => 'civicrm_batch.total',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'item_count' => array(
    +          ],
    +        ],
    +        'item_count' => [
               'name' => 'item_count',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Number of Items') ,
    -          'description' => 'Number of items in a batch.',
    +          'title' => ts('Batch Number of Items'),
    +          'description' => ts('Number of items in a batch.'),
    +          'where' => 'civicrm_batch.item_count',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'payment_instrument_id' => array(
    +          ],
    +        ],
    +        'payment_instrument_id' => [
               'name' => 'payment_instrument_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch Payment Method') ,
    -          'description' => 'fk to Payment Instrument options in civicrm_option_values',
    +          'title' => ts('Batch Payment Method'),
    +          'description' => ts('fk to Payment Instrument options in civicrm_option_values'),
    +          'where' => 'civicrm_batch.payment_instrument_id',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'payment_instrument',
                 'optionEditPath' => 'civicrm/admin/options/payment_instrument',
    -          )
    -        ) ,
    -        'exported_date' => array(
    +          ],
    +        ],
    +        'exported_date' => [
               'name' => 'exported_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Batch Exported Date') ,
    +          'title' => ts('Batch Exported Date'),
    +          'where' => 'civicrm_batch.exported_date',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -        ) ,
    -        'data' => array(
    +        ],
    +        'data' => [
               'name' => 'data',
               'type' => CRM_Utils_Type::T_LONGTEXT,
    -          'title' => ts('Batch Data') ,
    -          'description' => 'cache entered data',
    +          'title' => ts('Batch Data'),
    +          'description' => ts('cache entered data'),
    +          'where' => 'civicrm_batch.data',
               'table_name' => 'civicrm_batch',
               'entity' => 'Batch',
               'bao' => 'CRM_Batch_BAO_Batch',
               'localizable' => 0,
    -        ) ,
    -      );
    +        ],
    +      ];
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
         }
         return Civi::$statics[__CLASS__]['fields'];
       }
    +
       /**
        * Return a mapping from field-name to the corresponding key (as used in fields()).
        *
        * @return array
        *   Array(string $name => string $uniqueName).
        */
    -  static function &fieldKeys() {
    +  public static function &fieldKeys() {
         if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
           Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
         }
         return Civi::$statics[__CLASS__]['fieldKeys'];
       }
    +
       /**
        * Returns the names of this table
        *
        * @return string
        */
    -  static function getTableName() {
    +  public static function getTableName() {
         return CRM_Core_DAO::getLocaleTableName(self::$_tableName);
       }
    +
       /**
        * Returns if this table needs to be logged
        *
    -   * @return boolean
    +   * @return bool
        */
    -  function getLog() {
    +  public function getLog() {
         return self::$_log;
       }
    +
       /**
        * Returns the list of fields that can be imported
        *
    @@ -455,10 +472,11 @@ function getLog() {
        *
        * @return array
        */
    -  static function &import($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'batch', $prefix, array());
    +  public static function &import($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'batch', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of fields that can be exported
        *
    @@ -466,25 +484,31 @@ static function &import($prefix = false) {
        *
        * @return array
        */
    -  static function &export($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'batch', $prefix, array());
    +  public static function &export($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'batch', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of indices
    +   *
    +   * @param bool $localize
    +   *
    +   * @return array
        */
       public static function indices($localize = TRUE) {
    -    $indices = array(
    -      'UI_name' => array(
    +    $indices = [
    +      'UI_name' => [
             'name' => 'UI_name',
    -        'field' => array(
    +        'field' => [
               0 => 'name',
    -        ) ,
    -        'localizable' => false,
    -        'unique' => true,
    +        ],
    +        'localizable' => FALSE,
    +        'unique' => TRUE,
             'sig' => 'civicrm_batch::1::name',
    -      ) ,
    -    );
    +      ],
    +    ];
         return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
       }
    +
     }
    diff --git a/CRM/Batch/DAO/EntityBatch.php b/CRM/Batch/DAO/EntityBatch.php
    index 2ba28e1601e3..b491a4107895 100644
    --- a/CRM/Batch/DAO/EntityBatch.php
    +++ b/CRM/Batch/DAO/EntityBatch.php
    @@ -1,193 +1,185 @@
     __table = 'civicrm_entity_batch';
         parent::__construct();
       }
    +
       /**
        * Returns foreign keys and entity references.
        *
        * @return array
        *   [CRM_Core_Reference_Interface]
        */
    -  static function getReferenceColumns() {
    +  public static function getReferenceColumns() {
         if (!isset(Civi::$statics[__CLASS__]['links'])) {
    -      Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__);
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'batch_id', 'civicrm_batch', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName() , 'entity_id', NULL, 'id', 'entity_table');
    +      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'batch_id', 'civicrm_batch', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName(), 'entity_id', NULL, 'id', 'entity_table');
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
         }
         return Civi::$statics[__CLASS__]['links'];
       }
    +
       /**
        * Returns all the column names of this table
        *
        * @return array
        */
    -  static function &fields() {
    +  public static function &fields() {
         if (!isset(Civi::$statics[__CLASS__]['fields'])) {
    -      Civi::$statics[__CLASS__]['fields'] = array(
    -        'id' => array(
    +      Civi::$statics[__CLASS__]['fields'] = [
    +        'id' => [
               'name' => 'id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('EntityBatch ID') ,
    -          'description' => 'primary key',
    -          'required' => true,
    +          'title' => ts('EntityBatch ID'),
    +          'description' => ts('primary key'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_entity_batch.id',
               'table_name' => 'civicrm_entity_batch',
               'entity' => 'EntityBatch',
               'bao' => 'CRM_Batch_BAO_EntityBatch',
               'localizable' => 0,
    -        ) ,
    -        'entity_table' => array(
    +        ],
    +        'entity_table' => [
               'name' => 'entity_table',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('EntityBatch Table') ,
    -          'description' => 'physical tablename for entity being joined to file, e.g. civicrm_contact',
    +          'title' => ts('EntityBatch Table'),
    +          'description' => ts('physical tablename for entity being joined to file, e.g. civicrm_contact'),
               'maxlength' => 64,
               'size' => CRM_Utils_Type::BIG,
    +          'where' => 'civicrm_entity_batch.entity_table',
               'table_name' => 'civicrm_entity_batch',
               'entity' => 'EntityBatch',
               'bao' => 'CRM_Batch_BAO_EntityBatch',
               'localizable' => 0,
    -        ) ,
    -        'entity_id' => array(
    +        ],
    +        'entity_id' => [
               'name' => 'entity_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Entity ID') ,
    -          'description' => 'FK to entity table specified in entity_table column.',
    -          'required' => true,
    +          'title' => ts('Entity ID'),
    +          'description' => ts('FK to entity table specified in entity_table column.'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_entity_batch.entity_id',
               'table_name' => 'civicrm_entity_batch',
               'entity' => 'EntityBatch',
               'bao' => 'CRM_Batch_BAO_EntityBatch',
               'localizable' => 0,
    -        ) ,
    -        'batch_id' => array(
    +        ],
    +        'batch_id' => [
               'name' => 'batch_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Batch ID') ,
    -          'description' => 'FK to civicrm_batch',
    -          'required' => true,
    +          'title' => ts('Batch ID'),
    +          'description' => ts('FK to civicrm_batch'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_entity_batch.batch_id',
               'table_name' => 'civicrm_entity_batch',
               'entity' => 'EntityBatch',
               'bao' => 'CRM_Batch_BAO_EntityBatch',
               'localizable' => 0,
               'FKClassName' => 'CRM_Batch_DAO_Batch',
    -          'pseudoconstant' => array(
    +          'pseudoconstant' => [
                 'table' => 'civicrm_batch',
                 'keyColumn' => 'id',
                 'labelColumn' => 'title',
    -          )
    -        ) ,
    -      );
    +          ],
    +        ],
    +      ];
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
         }
         return Civi::$statics[__CLASS__]['fields'];
       }
    +
       /**
        * Return a mapping from field-name to the corresponding key (as used in fields()).
        *
        * @return array
        *   Array(string $name => string $uniqueName).
        */
    -  static function &fieldKeys() {
    +  public static function &fieldKeys() {
         if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
           Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
         }
         return Civi::$statics[__CLASS__]['fieldKeys'];
       }
    +
       /**
        * Returns the names of this table
        *
        * @return string
        */
    -  static function getTableName() {
    +  public static function getTableName() {
         return self::$_tableName;
       }
    +
       /**
        * Returns if this table needs to be logged
        *
    -   * @return boolean
    +   * @return bool
        */
    -  function getLog() {
    +  public function getLog() {
         return self::$_log;
       }
    +
       /**
        * Returns the list of fields that can be imported
        *
    @@ -195,10 +187,11 @@ function getLog() {
        *
        * @return array
        */
    -  static function &import($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'entity_batch', $prefix, array());
    +  public static function &import($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'entity_batch', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of fields that can be exported
        *
    @@ -206,36 +199,42 @@ static function &import($prefix = false) {
        *
        * @return array
        */
    -  static function &export($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'entity_batch', $prefix, array());
    +  public static function &export($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'entity_batch', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of indices
    +   *
    +   * @param bool $localize
    +   *
    +   * @return array
        */
       public static function indices($localize = TRUE) {
    -    $indices = array(
    -      'index_entity' => array(
    +    $indices = [
    +      'index_entity' => [
             'name' => 'index_entity',
    -        'field' => array(
    +        'field' => [
               0 => 'entity_table',
               1 => 'entity_id',
    -        ) ,
    -        'localizable' => false,
    +        ],
    +        'localizable' => FALSE,
             'sig' => 'civicrm_entity_batch::0::entity_table::entity_id',
    -      ) ,
    -      'UI_batch_entity' => array(
    +      ],
    +      'UI_batch_entity' => [
             'name' => 'UI_batch_entity',
    -        'field' => array(
    +        'field' => [
               0 => 'batch_id',
               1 => 'entity_id',
               2 => 'entity_table',
    -        ) ,
    -        'localizable' => false,
    -        'unique' => true,
    +        ],
    +        'localizable' => FALSE,
    +        'unique' => TRUE,
             'sig' => 'civicrm_entity_batch::1::batch_id::entity_id::entity_table',
    -      ) ,
    -    );
    +      ],
    +    ];
         return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
       }
    +
     }
    diff --git a/CRM/Batch/Form/Batch.php b/CRM/Batch/Form/Batch.php
    index 763be3cda1b4..4fb2753c19c5 100644
    --- a/CRM/Batch/Form/Batch.php
    +++ b/CRM/Batch/Form/Batch.php
    @@ -1,9 +1,9 @@
     _action & CRM_Core_Action::ADD) {
           // Set batch name default.
    @@ -104,7 +104,7 @@ public function postProcess() {
         }
     
         // always create with data entry status
    -    $params['status_id'] = CRM_Core_OptionGroup::getValue('batch_status', 'Data Entry', 'name');
    +    $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Batch_BAO_Batch', 'status_id', 'Data Entry');
         $batch = CRM_Batch_BAO_Batch::create($params);
     
         // redirect to batch entry page.
    diff --git a/CRM/Batch/Form/Entry.php b/CRM/Batch/Form/Entry.php
    index d96b337de2a7..2f279e509f64 100644
    --- a/CRM/Batch/Form/Entry.php
    +++ b/CRM/Batch/Form/Entry.php
    @@ -1,9 +1,9 @@
     _action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse');
     
         if (empty($this->_batchInfo)) {
    -      $params = array('id' => $this->_batchId);
    +      $params = ['id' => $this->_batchId];
           CRM_Batch_BAO_Batch::retrieve($params, $this->_batchInfo);
     
           $this->assign('batchTotal', !empty($this->_batchInfo['total']) ? $this->_batchInfo['total'] : NULL);
    @@ -102,10 +118,11 @@ public function preProcess() {
         }
         CRM_Core_Resources::singleton()
           ->addScriptFile('civicrm', 'templates/CRM/Batch/Form/Entry.js', 1, 'html-header')
    -      ->addSetting(array('batch' => array('type_id' => $this->_batchInfo['type_id'])))
    -      ->addSetting(array('setting' => array('monetaryThousandSeparator' => CRM_Core_Config::singleton()->monetaryThousandSeparator)))
    -      ->addSetting(array('setting' => array('monetaryDecimalPoint' => CRM_Core_Config::singleton()->monetaryDecimalPoint)));
    +      ->addSetting(['batch' => ['type_id' => $this->_batchInfo['type_id']]])
    +      ->addSetting(['setting' => ['monetaryThousandSeparator' => CRM_Core_Config::singleton()->monetaryThousandSeparator]])
    +      ->addSetting(['setting' => ['monetaryDecimalPoint' => CRM_Core_Config::singleton()->monetaryDecimalPoint]]);
     
    +    $this->assign('defaultCurrencySymbol', CRM_Core_BAO_Country::defaultCurrencySymbol());
       }
     
       /**
    @@ -127,7 +144,7 @@ public function buildQuickForm() {
     
         $this->addElement('hidden', 'batch_id', $this->_batchId);
     
    -    $batchTypes = CRM_Core_Pseudoconstant::get('CRM_Batch_DAO_Batch', 'type_id', array('flip' => 1), 'validate');
    +    $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
         // get the profile information
         if ($this->_batchInfo['type_id'] == $batchTypes['Contribution']) {
           CRM_Utils_System::setTitle(ts('Batch Data Entry for Contributions'));
    @@ -135,22 +152,17 @@ public function buildQuickForm() {
         }
         elseif ($this->_batchInfo['type_id'] == $batchTypes['Membership']) {
           CRM_Utils_System::setTitle(ts('Batch Data Entry for Memberships'));
    -      $customFields = CRM_Core_BAO_CustomField::getFields('Membership');
         }
         elseif ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment']) {
           CRM_Utils_System::setTitle(ts('Batch Data Entry for Pledge Payments'));
    -      $customFields = CRM_Core_BAO_CustomField::getFields('Contribution');
         }
    -    $this->_fields = array();
    +    $this->_fields = [];
         $this->_fields = CRM_Core_BAO_UFGroup::getFields($this->_profileId, FALSE, CRM_Core_Action::VIEW);
     
         // remove file type field and then limit fields
         $suppressFields = FALSE;
    -    $removehtmlTypes = array('File', 'Autocomplete-Select');
         foreach ($this->_fields as $name => $field) {
    -      if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) &&
    -        in_array($this->_fields[$name]['html_type'], $removehtmlTypes)
    -      ) {
    +      if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) && $this->_fields[$name]['html_type'] == 'Autocomplete-Select') {
             $suppressFields = TRUE;
             unset($this->_fields[$name]);
           }
    @@ -162,7 +174,7 @@ public function buildQuickForm() {
           }
         }
     
    -    $this->addFormRule(array('CRM_Batch_Form_Entry', 'formRule'), $this);
    +    $this->addFormRule(['CRM_Batch_Form_Entry', 'formRule'], $this);
     
         // add the force save button
         $forceSave = $this->getButtonName('upload', 'force');
    @@ -172,54 +184,51 @@ public function buildQuickForm() {
           ts('Ignore Mismatch & Process the Batch?')
         );
     
    -    $this->addButtons(array(
    -        array(
    -          'type' => 'upload',
    -          'name' => ts('Validate & Process the Batch'),
    -          'isDefault' => TRUE,
    -        ),
    -        array(
    -          'type' => 'cancel',
    -          'name' => ts('Save & Continue Later'),
    -        ),
    -      )
    -    );
    +    $this->addButtons([
    +      [
    +        'type' => 'upload',
    +        'name' => ts('Validate & Process the Batch'),
    +        'isDefault' => TRUE,
    +      ],
    +      [
    +        'type' => 'cancel',
    +        'name' => ts('Save & Continue Later'),
    +      ],
    +    ]);
     
         $this->assign('rowCount', $this->_batchInfo['item_count'] + 1);
     
    -    $fileFieldExists = FALSE;
    -    $preserveDefaultsArray = array(
    +    $preserveDefaultsArray = [
           'first_name',
           'last_name',
           'middle_name',
           'organization_name',
           'household_name',
    -    );
    +    ];
     
    -    $contactTypes = array('Contact', 'Individual', 'Household', 'Organization');
    -    $contactReturnProperties = array();
    -    $config = CRM_Core_Config::singleton();
    +    $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
    +    $contactReturnProperties = [];
     
         for ($rowNumber = 1; $rowNumber <= $this->_batchInfo['item_count']; $rowNumber++) {
    -      $this->addEntityRef("primary_contact_id[{$rowNumber}]", '', array(
    -          'create' => TRUE,
    -          'placeholder' => ts('- select -'),
    -        ));
    +      $this->addEntityRef("primary_contact_id[{$rowNumber}]", '', [
    +        'create' => TRUE,
    +        'placeholder' => ts('- select -'),
    +      ]);
     
           // special field specific to membership batch udpate
           if ($this->_batchInfo['type_id'] == 2) {
    -        $options = array(
    +        $options = [
               1 => ts('Add Membership'),
               2 => ts('Renew Membership'),
    -        );
    +        ];
             $this->add('select', "member_option[$rowNumber]", '', $options);
           }
           if ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment']) {
    -        $options = array('' => '-select-');
    -        $optionTypes = array(
    +        $options = ['' => '-select-'];
    +        $optionTypes = [
               '1' => ts('Adjust Pledge Payment Schedule?'),
               '2' => ts('Adjust Total Pledge Amount?'),
    -        );
    +        ];
             $this->add('select', "option_type[$rowNumber]", NULL, $optionTypes);
             if (!empty($this->_batchId) && !empty($this->_batchInfo['data']) && !empty($rowNumber)) {
               $dataValues = json_decode($this->_batchInfo['data'], TRUE);
    @@ -227,7 +236,7 @@ public function buildQuickForm() {
                 $pledgeIDs = CRM_Pledge_BAO_Pledge::getContactPledges($dataValues['values']['primary_contact_id'][$rowNumber]);
                 foreach ($pledgeIDs as $pledgeID) {
                   $pledgePayment = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($pledgeID);
    -              $options += array($pledgeID => CRM_Utils_Date::customFormat($pledgePayment['schedule_date'], '%m/%d/%Y') . ', ' . $pledgePayment['amount'] . ' ' . $pledgePayment['currency']);
    +              $options += [$pledgeID => CRM_Utils_Date::customFormat($pledgePayment['schedule_date'], '%m/%d/%Y') . ', ' . $pledgePayment['amount'] . ' ' . $pledgePayment['currency']];
                 }
               }
             }
    @@ -252,25 +261,27 @@ public function buildQuickForm() {
         // Notes: $this->_elementIndex gives an approximate count of the variables being sent
         // An offset value is set to deal with additional vars that are likely passed.
         // There may be a more accurate way to do this...
    -    $offset = 50; // set an offset to account for other vars we are not counting
    +    // set an offset to account for other vars we are not counting
    +    $offset = 50;
         if ((count($this->_elementIndex) + $offset) > ini_get("max_input_vars")) {
    -      CRM_Core_Error::fatal(ts('Batch size is too large. Increase value of php.ini setting "max_input_vars" (current val = ' . ini_get("max_input_vars") . ')'));
    +      // Avoiding 'ts' for obscure messages.
    +      CRM_Core_Error::fatal('Batch size is too large. Increase value of php.ini setting "max_input_vars" (current val = ' . ini_get("max_input_vars") . ')');
         }
     
         $this->assign('fields', $this->_fields);
         CRM_Core_Resources::singleton()
    -      ->addSetting(array(
    -        'contact' => array(
    +      ->addSetting([
    +        'contact' => [
               'return' => implode(',', $contactReturnProperties),
               'fieldmap' => array_flip($contactReturnProperties),
    -        ),
    -      ));
    +        ],
    +      ]);
     
         // don't set the status message when form is submitted.
         $buttonName = $this->controller->getButtonName('submit');
     
         if ($suppressFields && $buttonName != '_qf_Entry_next') {
    -      CRM_Core_Session::setStatus(ts("File or Autocomplete-Select type field(s) in the selected profile are not supported for Update multiple records."), ts('Some Fields Excluded'), 'info');
    +      CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple records."), ts('Some Fields Excluded'), 'info');
         }
       }
     
    @@ -288,24 +299,24 @@ public function buildQuickForm() {
        *   list of errors to be posted back to the form
        */
       public static function formRule($params, $files, $self) {
    -    $errors = array();
    -    $batchTypes = CRM_Core_Pseudoconstant::get('CRM_Batch_DAO_Batch', 'type_id', array('flip' => 1), 'validate');
    -    $fields = array(
    +    $errors = [];
    +    $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
    +    $fields = [
           'total_amount' => ts('Amount'),
           'financial_type' => ts('Financial Type'),
           'payment_instrument' => ts('Payment Method'),
    -    );
    +    ];
     
         //CRM-16480 if contact is selected, validate financial type and amount field.
         foreach ($params['field'] as $key => $value) {
           if (isset($value['trxn_id'])) {
    -        if (0 < CRM_Core_DAO::singleValueQuery('SELECT id FROM civicrm_contribution WHERE trxn_id = %1', array(1 => array($value['trxn_id'], 'String')))) {
    +        if (0 < CRM_Core_DAO::singleValueQuery('SELECT id FROM civicrm_contribution WHERE trxn_id = %1', [1 => [$value['trxn_id'], 'String']])) {
               $errors["field[$key][trxn_id]"] = ts('Transaction ID must be unique within the database');
             }
           }
           foreach ($fields as $field => $label) {
             if (!empty($params['primary_contact_id'][$key]) && empty($value[$field])) {
    -          $errors["field[$key][$field]"] = ts('%1 is a required field.', array(1 => $label));
    +          $errors["field[$key][$field]"] = ts('%1 is a required field.', [1 => $label]);
             }
           }
         }
    @@ -383,11 +394,11 @@ public function setDefaultValues() {
           $currentDate = date('Y-m-d H-i-s');
     
           $completeStatus = CRM_Contribute_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
    -      $specialFields = array(
    -        'join_date' => date('Y-m-d'),
    +      $specialFields = [
    +        'membership_join_date' => date('Y-m-d'),
             'receive_date' => $currentDate,
             'contribution_status_id' => $completeStatus,
    -      );
    +      ];
     
           for ($rowNumber = 1; $rowNumber <= $this->_batchInfo['item_count']; $rowNumber++) {
             foreach ($specialFields as $key => $value) {
    @@ -413,8 +424,8 @@ public function postProcess() {
         $params['actualBatchTotal'] = 0;
     
         // get the profile information
    -    $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', array('flip' => 1), 'validate');
    -    if (in_array($this->_batchInfo['type_id'], array($batchTypes['Pledge Payment'], $batchTypes['Contribution']))) {
    +    $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
    +    if (in_array($this->_batchInfo['type_id'], [$batchTypes['Pledge Payment'], $batchTypes['Contribution']])) {
           $this->processContribution($params);
         }
         elseif ($this->_batchInfo['type_id'] == $batchTypes['Membership']) {
    @@ -422,12 +433,12 @@ public function postProcess() {
         }
     
         // update batch to close status
    -    $paramValues = array(
    +    $paramValues = [
           'id' => $this->_batchId,
           // close status
    -      'status_id' => CRM_Core_OptionGroup::getValue('batch_status', 'Closed', 'name'),
    +      'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Batch_BAO_Batch', 'status_id', 'Closed'),
           'total' => $params['actualBatchTotal'],
    -    );
    +    ];
     
         CRM_Batch_BAO_Batch::create($paramValues);
     
    @@ -447,6 +458,14 @@ public function postProcess() {
        */
       private function processContribution(&$params) {
     
    +    foreach ($this->submittableMoneyFields as $moneyField) {
    +      foreach ($params['field'] as $index => $fieldValues) {
    +        if (isset($fieldValues[$moneyField])) {
    +          $params['field'][$index][$moneyField] = CRM_Utils_Rule::cleanMoney($params['field'][$index][$moneyField]);
    +        }
    +      }
    +    }
    +    $params['actualBatchTotal'] = CRM_Utils_Rule::cleanMoney($params['actualBatchTotal']);
         // get the price set associated with offline contribution record.
         $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name');
         $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
    @@ -501,13 +520,13 @@ private function processContribution(&$params) {
               $value['receipt_date'] = date('Y-m-d His');
             }
             // these translations & date handling are required because we are calling BAO directly rather than the api
    -        $fieldTranslations = array(
    +        $fieldTranslations = [
               'financial_type' => 'financial_type_id',
               'payment_instrument' => 'payment_instrument_id',
               'contribution_source' => 'source',
               'contribution_note' => 'note',
    -
    -        );
    +          'contribution_check_number' => 'check_number',
    +        ];
             foreach ($fieldTranslations as $formField => $baoField) {
               if (isset($value[$formField])) {
                 $value[$baoField] = $value[$formField];
    @@ -523,7 +542,7 @@ private function processContribution(&$params) {
             $this->_priceSet['fields'][$priceFieldID]['options'][$priceFieldValueID]['amount'] = $value['total_amount'];
             $value['price_' . $priceFieldID] = 1;
     
    -        $lineItem = array();
    +        $lineItem = [];
             CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], $value, $lineItem[$priceSetId]);
     
             // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel
    @@ -544,9 +563,10 @@ private function processContribution(&$params) {
               }
             }
             $value['line_item'] = $lineItem;
    +        $value['skipCleanMoney'] = TRUE;
             //finally call contribution create for all the magic
             $contribution = CRM_Contribute_BAO_Contribution::create($value);
    -        $batchTypes = CRM_Core_Pseudoconstant::get('CRM_Batch_DAO_Batch', 'type_id', array('flip' => 1), 'validate');
    +        $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
             if (!empty($this->_batchInfo['type_id']) && ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment'])) {
               $adjustTotalAmount = FALSE;
               if (isset($params['option_type'][$key])) {
    @@ -566,7 +586,7 @@ private function processContribution(&$params) {
                 }
                 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $pledgePaymentId, 'contribution_id', $contribution->id);
                 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeId,
    -              array($pledgePaymentId),
    +              [$pledgePaymentId],
                   $contribution->contribution_status_id,
                   NULL,
                   $contribution->total_amount,
    @@ -586,12 +606,12 @@ private function processContribution(&$params) {
                   $options[$value['product_name'][0]]
                 );
     
    -            $premiumParams = array(
    +            $premiumParams = [
                   'product_id' => $value['product_name'][0],
                   'contribution_id' => $contribution->id,
                   'product_option' => $value['product_option'],
                   'quantity' => 1,
    -            );
    +            ];
                 CRM_Contribute_BAO_Contribution::addPremium($premiumParams);
               }
             }
    @@ -631,8 +651,13 @@ private function processMembership(&$params) {
     
         if (isset($params['field'])) {
           // @todo - most of the wrangling in this function is because the api is not being used, especially date stuff.
    -      $customFields = array();
    +      $customFields = [];
           foreach ($params['field'] as $key => $value) {
    +        foreach ($value as $fieldKey => $fieldValue) {
    +          if (isset($this->_fields[$fieldKey]) && $this->_fields[$fieldKey]['data_type'] === 'Money') {
    +            $value[$fieldKey] = CRM_Utils_Rule::cleanMoney($fieldValue);
    +          }
    +        }
             // if contact is not selected we should skip the row
             if (empty($params['primary_contact_id'][$key])) {
               continue;
    @@ -698,9 +723,12 @@ private function processMembership(&$params) {
                 $value['soft_credit'][$key]['soft_credit_type_id'] = $params['soft_credit_type'][$key];
               }
               else {
    -            $value['soft_credit'][$key]['soft_credit_type_id'] = CRM_Core_OptionGroup::getValue('soft_credit_type', 'Gift', 'name');
    +            $value['soft_credit'][$key]['soft_credit_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', 'Gift');
               }
             }
    +        if (!empty($value['total_amount'])) {
    +          $value['total_amount'] = (float) $value['total_amount'];
    +        }
     
             $params['actualBatchTotal'] += $value['total_amount'];
     
    @@ -712,12 +740,12 @@ private function processMembership(&$params) {
     
             // make entry in line item for contribution
     
    -        $editedFieldParams = array(
    +        $editedFieldParams = [
               'price_set_id' => $priceSetId,
               'name' => $value['membership_type'][0],
    -        );
    +        ];
     
    -        $editedResults = array();
    +        $editedResults = [];
             CRM_Price_BAO_PriceField::retrieve($editedFieldParams, $editedResults);
     
             if (!empty($editedResults)) {
    @@ -725,12 +753,12 @@ private function processMembership(&$params) {
               $this->_priceSet['fields'][$editedResults['id']] = $priceSets['fields'][$editedResults['id']];
               unset($this->_priceSet['fields'][$editedResults['id']]['options']);
               $fid = $editedResults['id'];
    -          $editedFieldParams = array(
    +          $editedFieldParams = [
                 'price_field_id' => $editedResults['id'],
                 'membership_type_id' => $value['membership_type_id'],
    -          );
    +          ];
     
    -          $editedResults = array();
    +          $editedResults = [];
               CRM_Price_BAO_PriceFieldValue::retrieve($editedFieldParams, $editedResults);
               $this->_priceSet['fields'][$fid]['options'][$editedResults['id']] = $priceSets['fields'][$fid]['options'][$editedResults['id']];
               if (!empty($value['total_amount'])) {
    @@ -740,7 +768,7 @@ private function processMembership(&$params) {
               $fieldID = key($this->_priceSet['fields']);
               $value['price_' . $fieldID] = $editedResults['id'];
     
    -          $lineItem = array();
    +          $lineItem = [];
               CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'],
                 $value, $lineItem[$priceSetId]
               );
    @@ -776,10 +804,10 @@ private function processMembership(&$params) {
                 }
               }
     
    -          $formDates = array(
    +          $formDates = [
                 'end_date' => CRM_Utils_Array::value('membership_end_date', $value),
                 'start_date' => CRM_Utils_Array::value('membership_start_date', $value),
    -          );
    +          ];
               $membershipSource = CRM_Utils_Array::value('source', $value);
               list($membership) = CRM_Member_BAO_Membership::processMembership(
                 $value['contact_id'], $value['membership_type_id'], FALSE,
    @@ -789,30 +817,31 @@ private function processMembership(&$params) {
               );
     
               // make contribution entry
    -          $contrbutionParams = array_merge($value, array('membership_id' => $membership->id));
    +          $contrbutionParams = array_merge($value, ['membership_id' => $membership->id]);
    +          $contrbutionParams['skipCleanMoney'] = TRUE;
               // @todo - calling this from here is pretty hacky since it is called from membership.create anyway
               // This form should set the correct params & not call this fn directly.
               CRM_Member_BAO_Membership::recordMembershipContribution($contrbutionParams);
             }
             else {
    -          $dateTypes = array(
    -            'join_date' => 'joinDate',
    +          $dateTypes = [
    +            'membership_join_date' => 'joinDate',
                 'membership_start_date' => 'startDate',
                 'membership_end_date' => 'endDate',
    -          );
    +          ];
     
    -          $dates = array(
    +          $dates = [
                 'join_date',
                 'start_date',
                 'end_date',
                 'reminder_date',
    -          );
    +          ];
               foreach ($dateTypes as $dateField => $dateVariable) {
                 $$dateVariable = CRM_Utils_Date::processDate($value[$dateField]);
                 $fDate[$dateField] = CRM_Utils_Array::value($dateField, $value);
               }
     
    -          $calcDates = array();
    +          $calcDates = [];
               $calcDates[$membershipTypeId] = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membershipTypeId,
                 $joinDate, $startDate, $endDate
               );
    @@ -831,7 +860,8 @@ private function processMembership(&$params) {
     
               unset($value['membership_start_date']);
               unset($value['membership_end_date']);
    -          $ids = array();
    +          $ids = [];
    +          // @todo stop passing empty $ids
               $membership = CRM_Member_BAO_Membership::create($value, $ids);
             }
     
    @@ -846,12 +876,12 @@ private function processMembership(&$params) {
                   $options[$value['product_name'][0]]
                 );
     
    -            $premiumParams = array(
    +            $premiumParams = [
                   'product_id' => $value['product_name'][0],
                   'contribution_id' => CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $membership->id, 'contribution_id', 'membership_id'),
                   'product_option' => $value['product_option'],
                   'quantity' => 1,
    -            );
    +            ];
                 CRM_Contribute_BAO_Contribution::addPremium($premiumParams);
               }
             }
    diff --git a/CRM/Batch/Form/Search.php b/CRM/Batch/Form/Search.php
    index 42f218967999..61b86a5f862b 100644
    --- a/CRM/Batch/Form/Search.php
    +++ b/CRM/Batch/Form/Search.php
    @@ -1,9 +1,9 @@
     addButtons(
    -      array(
    -        array(
    +      [
    +        [
               'type' => 'refresh',
               'name' => ts('Search'),
               'isDefault' => TRUE,
    -        ),
    -      )
    +        ],
    +      ]
         );
     
         parent::buildQuickForm();
    diff --git a/CRM/Batch/Page/AJAX.php b/CRM/Batch/Page/AJAX.php
    index e7f199182dd0..3d840a48cc09 100644
    --- a/CRM/Batch/Page/AJAX.php
    +++ b/CRM/Batch/Page/AJAX.php
    @@ -1,9 +1,9 @@
      $_POST)));
    +    CRM_Core_DAO::setFieldValue('CRM_Batch_DAO_Batch', $batchId, 'data', json_encode(['values' => $_POST]));
     
         CRM_Utils_System::civiExit();
       }
    @@ -54,21 +54,32 @@ public function batchSave() {
        * @deprecated
        */
       public static function getBatchList() {
    -    $sortMapper = array(
    -      0 => 'batch.title',
    -      1 => 'batch.type_id',
    -      2 => '',
    -      3 => 'batch.total',
    -      4 => 'batch.status_id',
    -      5 => '',
    -    );
    -
    +    $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric');
    +    if ($context != 'financialBatch') {
    +      $sortMapper = [
    +        0 => 'title',
    +        1 => 'type_id.label',
    +        2 => 'item_count',
    +        3 => 'total',
    +        4 => 'status_id.label',
    +        5 => 'created_id.sort_name',
    +      ];
    +    }
    +    else {
    +      $sortMapper = [
    +        1 => 'title',
    +        2 => 'payment_instrument_id.label',
    +        3 => 'item_count',
    +        4 => 'total',
    +        5 => 'status_id.label',
    +        6 => 'created_id.sort_name',
    +      ];
    +    }
         $sEcho = CRM_Utils_Type::escape($_REQUEST['sEcho'], 'Integer');
         $offset = isset($_REQUEST['iDisplayStart']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayStart'], 'Integer') : 0;
         $rowCount = isset($_REQUEST['iDisplayLength']) ? CRM_Utils_Type::escape($_REQUEST['iDisplayLength'], 'Integer') : 25;
         $sort = isset($_REQUEST['iSortCol_0']) ? CRM_Utils_Array::value(CRM_Utils_Type::escape($_REQUEST['iSortCol_0'], 'Integer'), $sortMapper) : NULL;
         $sortOrder = isset($_REQUEST['sSortDir_0']) ? CRM_Utils_Type::escape($_REQUEST['sSortDir_0'], 'String') : 'asc';
    -    $context = isset($_REQUEST['context']) ? CRM_Utils_Type::escape($_REQUEST['context'], 'String') : NULL;
     
         $params = $_REQUEST;
         if ($sort && $sortOrder) {
    @@ -80,7 +91,7 @@ public static function getBatchList() {
     
         if ($context != 'financialBatch') {
           // data entry status batches
    -      $params['status_id'] = CRM_Core_OptionGroup::getValue('batch_status', 'Data Entry', 'name');
    +      $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Batch_BAO_Batch', 'status_id', 'Data Entry');
         }
     
         $params['context'] = $context;
    @@ -91,7 +102,7 @@ public static function getBatchList() {
         $iFilteredTotal = $iTotal = $params['total'];
     
         if ($context == 'financialBatch') {
    -      $selectorElements = array(
    +      $selectorElements = [
             'check',
             'batch_name',
             'payment_instrument',
    @@ -100,10 +111,10 @@ public static function getBatchList() {
             'status',
             'created_by',
             'links',
    -      );
    +      ];
         }
         else {
    -      $selectorElements = array(
    +      $selectorElements = [
             'batch_name',
             'type',
             'item_count',
    @@ -111,7 +122,7 @@ public static function getBatchList() {
             'status',
             'created_by',
             'links',
    -      );
    +      ];
         }
         CRM_Utils_System::setHttpHeader('Content-Type', 'application/json');
         echo CRM_Utils_JSON::encodeDataTableSelector($batches, $sEcho, $iTotal, $iFilteredTotal, $selectorElements);
    diff --git a/CRM/Batch/Page/Batch.php b/CRM/Batch/Page/Batch.php
    index ddbe0338eec6..6267d5d90dff 100644
    --- a/CRM/Batch/Page/Batch.php
    +++ b/CRM/Batch/Page/Batch.php
    @@ -1,9 +1,9 @@
      1);
    +    $groupParams['group_type'] = ['2' => 1];
         self::updateCiviGroup($groupParams, $op);
     
         if (CRM_Bridge_OG_Utils::aclEnabled()) {
    @@ -55,7 +55,7 @@ public static function nodeapi(&$params, $op) {
           $aclParams = $params;
           $aclParams['name'] = $aclParams['title'] = "{$aclParams['name']}: Administrator";
           $aclParams['source'] = CRM_Bridge_OG_Utils::ogSyncACLName($params['og_id']);
    -      $aclParams['group_type'] = array('1');
    +      $aclParams['group_type'] = ['1'];
           self::updateCiviGroup($aclParams, $op);
     
           $aclParams['acl_group_id'] = $aclParams['group_id'];
    @@ -138,7 +138,7 @@ public static function updateCiviACLRole(&$params, $op) {
         $dao->label = $params['title'];
         $dao->is_active = 1;
     
    -    $weightParams = array('option_group_id' => $optionGroupID);
    +    $weightParams = ['option_group_id' => $optionGroupID];
         $dao->weight = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue',
           $weightParams
         );
    @@ -153,10 +153,10 @@ public static function updateCiviACLRole(&$params, $op) {
      WHERE v.option_group_id = %1
        AND v.description     = %2
     ";
    -    $queryParams = array(
    -      1 => array($optionGroupID, 'Integer'),
    -      2 => array($params['source'], 'String'),
    -    );
    +    $queryParams = [
    +      1 => [$optionGroupID, 'Integer'],
    +      2 => [$params['source'], 'String'],
    +    ];
         $dao->id = CRM_Core_DAO::singleValueQuery($query, $queryParams);
         $dao->save();
         $params['acl_role_id'] = $dao->value;
    @@ -228,11 +228,11 @@ public static function og(&$params, $op) {
           NULL, TRUE
         );
     
    -    $groupParams = array(
    +    $groupParams = [
           'contact_id' => $contactID,
           'group_id' => $groupID,
           'version' => 3,
    -    );
    +    ];
     
         if ($op == 'add') {
           $groupParams['status'] = $params['is_active'] ? 'Added' : 'Pending';
    @@ -251,12 +251,12 @@ public static function og(&$params, $op) {
             NULL, TRUE
           );
     
    -      $groupParams = array(
    +      $groupParams = [
             'contact_id' => $contactID,
             'group_id' => $groupID,
             'status' => $params['is_admin'] ? 'Added' : 'Removed',
             'version' => 3,
    -      );
    +      ];
     
           if ($params['is_admin']) {
             civicrm_api('GroupContact', 'Create', $groupParams);
    diff --git a/CRM/Bridge/OG/Utils.php b/CRM/Bridge/OG/Utils.php
    index a1fc56e9a828..154bf833082e 100644
    --- a/CRM/Bridge/OG/Utils.php
    +++ b/CRM/Bridge/OG/Utils.php
    @@ -1,9 +1,9 @@
      array($source, 'String'));
    +    $params = [1 => [$source, 'String']];
     
         if ($title) {
           $query .= " OR title = %2";
    -      $params[2] = array($title, 'String');
    +      $params[2] = [$title, 'String'];
         }
     
         $groupID = CRM_Core_DAO::singleValueQuery($query, $params);
    diff --git a/CRM/Campaign/BAO/Campaign.php b/CRM/Campaign/BAO/Campaign.php
    index 43c534f5ef4d..c7a9f25e9d75 100644
    --- a/CRM/Campaign/BAO/Campaign.php
    +++ b/CRM/Campaign/BAO/Campaign.php
    @@ -1,9 +1,9 @@
     get('userID');
           }
     
    -      if (!(CRM_Utils_Array::value('created_date', $params))) {
    +      if (empty($params['created_date'])) {
             $params['created_date'] = date('YmdHis');
           }
     
    -      if (!(CRM_Utils_Array::value('name', $params))) {
    +      if (empty($params['name'])) {
             $params['name'] = CRM_Utils_String::titleToVar($params['title'], 64);
           }
     
    @@ -93,7 +93,6 @@ public static function create(&$params) {
             $dao->entity_id = $entityId;
             $dao->group_type = 'Include';
             $dao->save();
    -        $dao->free();
           }
         }
     
    @@ -182,14 +181,14 @@ public static function getCampaigns(
       ) {
         static $campaigns;
         $cacheKey = 0;
    -    $cacheKeyParams = array(
    +    $cacheKeyParams = [
           'includeId',
           'excludeId',
           'onlyActive',
           'onlyCurrent',
           'appendDatesToTitle',
           'forceAll',
    -    );
    +    ];
         foreach ($cacheKeyParams as $param) {
           $cacheParam = $$param;
           if (!$cacheParam) {
    @@ -199,7 +198,7 @@ public static function getCampaigns(
         }
     
         if (!isset($campaigns[$cacheKey])) {
    -      $where = array('( camp.title IS NOT NULL )');
    +      $where = ['( camp.title IS NOT NULL )'];
           if ($excludeId) {
             $where[] = "( camp.id != $excludeId )";
           }
    @@ -229,14 +228,14 @@ public static function getCampaigns(
     Order By  camp.title";
     
           $campaign = CRM_Core_DAO::executeQuery($query);
    -      $campaigns[$cacheKey] = array();
    +      $campaigns[$cacheKey] = [];
           $config = CRM_Core_Config::singleton();
     
           while ($campaign->fetch()) {
             $title = $campaign->title;
             if ($appendDatesToTitle) {
    -          $dates = array();
    -          foreach (array('start_date', 'end_date') as $date) {
    +          $dates = [];
    +          foreach (['start_date', 'end_date'] as $date) {
                 if ($campaign->$date) {
                   $dates[] = CRM_Utils_Date::customFormat($campaign->$date, $config->dateformatFull);
                 }
    @@ -278,7 +277,7 @@ public static function getPermissionedCampaigns(
         $doCheckForPermissions = TRUE
       ) {
         $cacheKey = 0;
    -    $cachekeyParams = array(
    +    $cachekeyParams = [
           'includeId',
           'excludeId',
           'onlyActive',
    @@ -287,7 +286,7 @@ public static function getPermissionedCampaigns(
           'doCheckForComponent',
           'doCheckForPermissions',
           'forceAll',
    -    );
    +    ];
         foreach ($cachekeyParams as $param) {
           $cacheKeyParam = $$param;
           if (!$cacheKeyParam) {
    @@ -299,11 +298,11 @@ public static function getPermissionedCampaigns(
         static $validCampaigns;
         if (!isset($validCampaigns[$cacheKey])) {
           $isValid = TRUE;
    -      $campaigns = array(
    -        'campaigns' => array(),
    +      $campaigns = [
    +        'campaigns' => [],
             'hasAccessCampaign' => FALSE,
             'isCampaignEnabled' => FALSE,
    -      );
    +      ];
     
           //do check for component.
           if ($doCheckForComponent) {
    @@ -359,18 +358,18 @@ public static function isCampaignEnable() {
        *
        * @return array|int
        */
    -  public static function getCampaignSummary($params = array(), $onlyCount = FALSE) {
    -    $campaigns = array();
    +  public static function getCampaignSummary($params = [], $onlyCount = FALSE) {
    +    $campaigns = [];
     
         //build the limit and order clause.
         $limitClause = $orderByClause = $lookupTableJoins = NULL;
         if (!$onlyCount) {
    -      $sortParams = array(
    +      $sortParams = [
             'sort' => 'start_date',
             'offset' => 0,
             'rowCount' => 10,
             'sortOrder' => 'desc',
    -      );
    +      ];
           foreach ($sortParams as $name => $default) {
             if (!empty($params[$name])) {
               $sortParams[$name] = $params[$name];
    @@ -404,32 +403,32 @@ public static function getCampaignSummary($params = array(), $onlyCount = FALSE)
         }
     
         //build the where clause.
    -    $queryParams = $where = array();
    +    $queryParams = $where = [];
         if (!empty($params['id'])) {
           $where[] = "( campaign.id = %1 )";
    -      $queryParams[1] = array($params['id'], 'Positive');
    +      $queryParams[1] = [$params['id'], 'Positive'];
         }
         if (!empty($params['name'])) {
           $where[] = "( campaign.name LIKE %2 )";
    -      $queryParams[2] = array('%' . trim($params['name']) . '%', 'String');
    +      $queryParams[2] = ['%' . trim($params['name']) . '%', 'String'];
         }
         if (!empty($params['title'])) {
           $where[] = "( campaign.title LIKE %3 )";
    -      $queryParams[3] = array('%' . trim($params['title']) . '%', 'String');
    +      $queryParams[3] = ['%' . trim($params['title']) . '%', 'String'];
         }
         if (!empty($params['start_date'])) {
           $startDate = CRM_Utils_Date::processDate($params['start_date']);
           $where[] = "( campaign.start_date >= %4 OR campaign.start_date IS NULL )";
    -      $queryParams[4] = array($startDate, 'String');
    +      $queryParams[4] = [$startDate, 'String'];
         }
         if (!empty($params['end_date'])) {
           $endDate = CRM_Utils_Date::processDate($params['end_date'], '235959');
           $where[] = "( campaign.end_date <= %5 OR campaign.end_date IS NULL )";
    -      $queryParams[5] = array($endDate, 'String');
    +      $queryParams[5] = [$endDate, 'String'];
         }
         if (!empty($params['description'])) {
           $where[] = "( campaign.description LIKE %6 )";
    -      $queryParams[6] = array('%' . trim($params['description']) . '%', 'String');
    +      $queryParams[6] = ['%' . trim($params['description']) . '%', 'String'];
         }
         if (!empty($params['campaign_type_id'])) {
           $typeId = $params['campaign_type_id'];
    @@ -457,7 +456,7 @@ public static function getCampaignSummary($params = array(), $onlyCount = FALSE)
           $whereClause = ' WHERE ' . implode(" \nAND ", $where);
         }
     
    -    $properties = array(
    +    $properties = [
           'id',
           'name',
           'title',
    @@ -467,7 +466,7 @@ public static function getCampaignSummary($params = array(), $onlyCount = FALSE)
           'is_active',
           'description',
           'campaign_type_id',
    -    );
    +    ];
     
         $selectClause = '
     SELECT  campaign.id               as id,
    @@ -520,11 +519,11 @@ public static function getCampaignCount() {
       public static function getCampaignGroups($campaignId) {
         static $campaignGroups;
         if (!$campaignId) {
    -      return array();
    +      return [];
         }
     
         if (!isset($campaignGroups[$campaignId])) {
    -      $campaignGroups[$campaignId] = array();
    +      $campaignGroups[$campaignId] = [];
     
           $query = "
         SELECT  grp.title, grp.id
    @@ -534,7 +533,7 @@ public static function getCampaignGroups($campaignId) {
            AND  campgrp.entity_table = 'civicrm_group'
            AND  campgrp.campaign_id = %1";
     
    -      $groups = CRM_Core_DAO::executeQuery($query, array(1 => array($campaignId, 'Positive')));
    +      $groups = CRM_Core_DAO::executeQuery($query, [1 => [$campaignId, 'Positive']]);
           while ($groups->fetch()) {
             $campaignGroups[$campaignId][$groups->id] = $groups->title;
           }
    @@ -551,8 +550,8 @@ public static function getCampaignGroups($campaignId) {
        * @param bool $is_active
        *   Value we want to set the is_active field.
        *
    -   * @return CRM_Campaign_DAO_Campaign|null
    -   *   DAO object on success, null otherwise
    +   * @return bool
    +   *   true if we found and updated the object, else false
        */
       public static function setIsActive($id, $is_active) {
         return CRM_Core_DAO::setFieldValue('CRM_Campaign_DAO_Campaign', $id, 'is_active', $is_active);
    @@ -591,72 +590,31 @@ public static function addCampaign(&$form, $connectedCampaignId = NULL) {
         }
     
         $campaignDetails = self::getPermissionedCampaigns($connectedCampaignId, NULL, TRUE, TRUE, $appendDates);
    -    $fields = array('campaigns', 'hasAccessCampaign', 'isCampaignEnabled');
    +    $fields = ['campaigns', 'hasAccessCampaign', 'isCampaignEnabled'];
         foreach ($fields as $fld) {
           $$fld = CRM_Utils_Array::value($fld, $campaignDetails);
         }
     
    -    //lets see do we have past campaigns.
    -    $hasPastCampaigns = FALSE;
    -    $allActiveCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, TRUE, FALSE);
    -    if (count($allActiveCampaigns) > count($campaigns)) {
    -      $hasPastCampaigns = TRUE;
    -    }
    -    $hasCampaigns = FALSE;
    -    if (!empty($campaigns)) {
    -      $hasCampaigns = TRUE;
    -    }
    -    if ($hasPastCampaigns) {
    -      $hasCampaigns = TRUE;
    -      $form->add('hidden', 'included_past_campaigns');
    -    }
    -
         $showAddCampaign = FALSE;
    -    $alreadyIncludedPastCampaigns = FALSE;
         if ($connectedCampaignId || ($isCampaignEnabled && $hasAccessCampaign)) {
           $showAddCampaign = TRUE;
    -      //lets add past campaigns as options to quick-form element.
    -      if ($hasPastCampaigns && $form->getElementValue('included_past_campaigns')) {
    -        $campaigns = $allActiveCampaigns;
    -        $alreadyIncludedPastCampaigns = TRUE;
    -      }
    -      $campaign = &$form->add('select',
    -        'campaign_id',
    -        ts('Campaign'),
    -        array('' => ts('- select -')) + $campaigns,
    -        FALSE,
    -        array('class' => 'crm-select2')
    -      );
    +      $campaign = $form->addEntityRef('campaign_id', ts('Campaign'), [
    +        'entity' => 'Campaign',
    +        'create' => TRUE,
    +        'select' => ['minimumInputLength' => 0],
    +      ]);
           //lets freeze when user does not has access or campaign is disabled.
           if (!$isCampaignEnabled || !$hasAccessCampaign) {
             $campaign->freeze();
           }
         }
     
    -    $addCampaignURL = NULL;
    -    if (empty($campaigns) && $hasAccessCampaign && $isCampaignEnabled) {
    -      $addCampaignURL = CRM_Utils_System::url('civicrm/campaign/add', 'reset=1');
    -    }
    -
    -    $includePastCampaignURL = NULL;
    -    if ($hasPastCampaigns && $isCampaignEnabled && $hasAccessCampaign) {
    -      $includePastCampaignURL = CRM_Utils_System::url('civicrm/ajax/rest',
    -        'className=CRM_Campaign_Page_AJAX&fnName=allActiveCampaigns',
    -        FALSE, NULL, FALSE
    -      );
    -    }
    -
         //carry this info to templates.
    -    $infoFields = array(
    -      'hasCampaigns',
    -      'addCampaignURL',
    +    $infoFields = [
           'showAddCampaign',
    -      'hasPastCampaigns',
           'hasAccessCampaign',
           'isCampaignEnabled',
    -      'includePastCampaignURL',
    -      'alreadyIncludedPastCampaigns',
    -    );
    +    ];
         foreach ($infoFields as $fld) {
           $campaignInfo[$fld] = $$fld;
         }
    @@ -671,9 +629,9 @@ public static function addCampaign(&$form, $connectedCampaignId = NULL) {
        * @param string $elementName
        */
       public static function addCampaignInComponentSearch(&$form, $elementName = 'campaign_id') {
    -    $campaignInfo = array();
    +    $campaignInfo = [];
         $campaignDetails = self::getPermissionedCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE);
    -    $fields = array('campaigns', 'hasAccessCampaign', 'isCampaignEnabled');
    +    $fields = ['campaigns', 'hasAccessCampaign', 'isCampaignEnabled'];
         foreach ($fields as $fld) {
           $$fld = CRM_Utils_Array::value($fld, $campaignDetails);
         }
    @@ -682,29 +640,86 @@ public static function addCampaignInComponentSearch(&$form, $elementName = 'camp
           //get the current campaign only.
           $currentCampaigns = self::getCampaigns(NULL, NULL, FALSE);
           $pastCampaigns = array_diff($campaigns, $currentCampaigns);
    -      $allCampaigns = array();
    +      $allCampaigns = [];
           if (!empty($currentCampaigns)) {
    -        $allCampaigns = array('crm_optgroup_current_campaign' => ts('Current Campaigns')) + $currentCampaigns;
    +        $allCampaigns = ['crm_optgroup_current_campaign' => ts('Current Campaigns')] + $currentCampaigns;
           }
           if (!empty($pastCampaigns)) {
    -        $allCampaigns += array('crm_optgroup_past_campaign' => ts('Past Campaigns')) + $pastCampaigns;
    +        $allCampaigns += ['crm_optgroup_past_campaign' => ts('Past Campaigns')] + $pastCampaigns;
           }
     
           $showCampaignInSearch = TRUE;
           $form->add('select', $elementName, ts('Campaigns'), $allCampaigns, FALSE,
    -        array('id' => 'campaigns', 'multiple' => 'multiple', 'class' => 'crm-select2')
    +        ['id' => 'campaigns', 'multiple' => 'multiple', 'class' => 'crm-select2']
           );
         }
    -    $infoFields = array(
    +    $infoFields = [
           'elementName',
           'hasAccessCampaign',
           'isCampaignEnabled',
           'showCampaignInSearch',
    -    );
    +    ];
         foreach ($infoFields as $fld) {
           $campaignInfo[$fld] = $$fld;
         }
         $form->assign('campaignInfo', $campaignInfo);
       }
     
    +  /**
    +   * @return array
    +   */
    +  public static function getEntityRefFilters() {
    +    return [
    +      ['key' => 'campaign_type_id', 'value' => ts('Campaign Type')],
    +      ['key' => 'status_id', 'value' => ts('Status')],
    +      [
    +        'key' => 'start_date',
    +        'value' => ts('Start Date'),
    +        'options' => [
    +          ['key' => '{">":"now"}', 'value' => ts('Upcoming')],
    +          [
    +            'key' => '{"BETWEEN":["now - 3 month","now"]}',
    +            'value' => ts('Past 3 Months'),
    +          ],
    +          [
    +            'key' => '{"BETWEEN":["now - 6 month","now"]}',
    +            'value' => ts('Past 6 Months'),
    +          ],
    +          [
    +            'key' => '{"BETWEEN":["now - 1 year","now"]}',
    +            'value' => ts('Past Year'),
    +          ],
    +        ],
    +      ],
    +      [
    +        'key' => 'end_date',
    +        'value' => ts('End Date'),
    +        'options' => [
    +          ['key' => '{">":"now"}', 'value' => ts('In the future')],
    +          ['key' => '{"<":"now"}', 'value' => ts('In the past')],
    +          ['key' => '{"IS NULL":"1"}', 'value' => ts('Not set')],
    +        ],
    +      ],
    +    ];
    +  }
    +
    +  /**
    +   * Links to create new campaigns from entityRef widget
    +   *
    +   * @return array|bool
    +   */
    +  public static function getEntityRefCreateLinks() {
    +    if (CRM_Core_Permission::check([['administer CiviCampaign', 'manage campaign']])) {
    +      return [
    +        [
    +          'label' => ts('New Campaign'),
    +          'url' => CRM_Utils_System::url('civicrm/campaign/add', "reset=1",
    +            NULL, NULL, FALSE, FALSE, TRUE),
    +          'type' => 'Campaign',
    +        ],
    +      ];
    +    }
    +    return FALSE;
    +  }
    +
     }
    diff --git a/CRM/Campaign/BAO/Petition.php b/CRM/Campaign/BAO/Petition.php
    index 2f85010a56ec..a9159061a0e9 100644
    --- a/CRM/Campaign/BAO/Petition.php
    +++ b/CRM/Campaign/BAO/Petition.php
    @@ -1,9 +1,9 @@
      'created_date',
             'offset' => 0,
             'rowCount' => 10,
             'sortOrder' => 'desc',
    -      );
    +      ];
           foreach ($sortParams as $name => $default) {
             if (!empty($params[$name])) {
               $sortParams[$name] = $params[$name];
    @@ -90,22 +91,22 @@ public static function getPetitionSummary($params = array(), $onlyCount = FALSE)
         }
     
         //build the where clause.
    -    $queryParams = $where = array();
    +    $queryParams = $where = [];
     
         //we only have activity type as a
         //difference between survey and petition.
    -    $petitionTypeID = CRM_Core_OptionGroup::getValue('activity_type', 'petition', 'name');
    +    $petitionTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Petition');
         if ($petitionTypeID) {
           $where[] = "( petition.activity_type_id = %1 )";
    -      $queryParams[1] = array($petitionTypeID, 'Positive');
    +      $queryParams[1] = [$petitionTypeID, 'Positive'];
         }
         if (!empty($params['title'])) {
           $where[] = "( petition.title LIKE %2 )";
    -      $queryParams[2] = array('%' . trim($params['title']) . '%', 'String');
    +      $queryParams[2] = ['%' . trim($params['title']) . '%', 'String'];
         }
         if (!empty($params['campaign_id'])) {
           $where[] = '( petition.campaign_id = %3 )';
    -      $queryParams[3] = array($params['campaign_id'], 'Positive');
    +      $queryParams[3] = [$params['campaign_id'], 'Positive'];
         }
         $whereClause = NULL;
         if (!empty($where)) {
    @@ -132,8 +133,8 @@ public static function getPetitionSummary($params = array(), $onlyCount = FALSE)
           return (int) CRM_Core_DAO::singleValueQuery($query, $queryParams);
         }
     
    -    $petitions = array();
    -    $properties = array(
    +    $petitions = [];
    +    $properties = [
           'id',
           'title',
           'campaign_id',
    @@ -141,7 +142,7 @@ public static function getPetitionSummary($params = array(), $onlyCount = FALSE)
           'is_default',
           'result_id',
           'activity_type_id',
    -    );
    +    ];
     
         $petition = CRM_Core_DAO::executeQuery($query, $queryParams);
         while ($petition->fetch()) {
    @@ -159,11 +160,11 @@ public static function getPetitionSummary($params = array(), $onlyCount = FALSE)
        */
       public static function getPetitionCount() {
         $whereClause = 'WHERE ( 1 )';
    -    $queryParams = array();
    -    $petitionTypeID = CRM_Core_OptionGroup::getValue('activity_type', 'petition', 'name');
    +    $queryParams = [];
    +    $petitionTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Petition');
         if ($petitionTypeID) {
           $whereClause = "WHERE ( petition.activity_type_id = %1 )";
    -      $queryParams[1] = array($petitionTypeID, 'Positive');
    +      $queryParams[1] = [$petitionTypeID, 'Positive'];
         }
         $query = "SELECT COUNT(*) FROM civicrm_survey petition {$whereClause}";
     
    @@ -176,7 +177,8 @@ public static function getPetitionCount() {
        * @param array $params
        *   (reference ) an assoc array of name/value pairs.
        *
    -   * @return CRM_Campaign_BAO_Petition
    +   * @return mixed
    +   *   CRM_Campaign_BAO_Petition or NULl or void
        */
       public function createSignature(&$params) {
         if (empty($params)) {
    @@ -198,7 +200,7 @@ public function createSignature(&$params) {
           // create activity
           // 1-Schedule, 2-Completed
     
    -      $activityParams = array(
    +      $activityParams = [
             'source_contact_id' => $params['contactId'],
             'target_contact_id' => $params['contactId'],
             'source_record_id' => $params['sid'],
    @@ -207,7 +209,7 @@ public function createSignature(&$params) {
             'activity_date_time' => date("YmdHis"),
             'status_id' => $params['statusId'],
             'activity_campaign_id' => $params['activity_campaign_id'],
    -      );
    +      ];
     
           //activity creation
           // *** check for activity using source id - if already signed
    @@ -240,13 +242,13 @@ public function confirmSignature($activity_id, $contact_id, $petition_id) {
         // change activity status to completed (status_id = 2)
         // I wonder why do we need contact_id when we have activity_id anyway? [chastell]
         $sql = 'UPDATE civicrm_activity SET status_id = 2 WHERE id = %1';
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
    -    $params = array(
    -      1 => array($activity_id, 'Integer'),
    -      2 => array($contact_id, 'Integer'),
    -      3 => array($sourceID, 'Integer'),
    -    );
    +    $params = [
    +      1 => [$activity_id, 'Integer'],
    +      2 => [$contact_id, 'Integer'],
    +      3 => [$sourceID, 'Integer'],
    +    ];
         CRM_Core_DAO::executeQuery($sql, $params);
     
         $sql = 'UPDATE civicrm_activity_contact SET contact_id = %2 WHERE activity_id = %1 AND record_type_id = %3';
    @@ -259,10 +261,10 @@ public function confirmSignature($activity_id, $contact_id, $petition_id) {
     WHERE       entity_table = 'civicrm_contact'
     AND         entity_id = %1
     AND         tag_id = ( SELECT id FROM civicrm_tag WHERE name = %2 )";
    -    $params = array(
    -      1 => array($contact_id, 'Integer'),
    -      2 => array($tag_name, 'String'),
    -    );
    +    $params = [
    +      1 => [$contact_id, 'Integer'],
    +      2 => [$tag_name, 'String'],
    +    ];
         CRM_Core_DAO::executeQuery($sql, $params);
         // validate arguments to setcookie are numeric to prevent header manipulation
         if (isset($petition_id) && is_numeric($petition_id)
    @@ -293,7 +295,7 @@ public function confirmSignature($activity_id, $contact_id, $petition_id) {
        * @return array
        */
       public static function getPetitionSignatureTotalbyCountry($surveyId) {
    -    $countries = array();
    +    $countries = [];
         $sql = "
                 SELECT count(civicrm_address.country_id) as total,
                     IFNULL(country_id,'') as country_id,IFNULL(iso_code,'') as country_iso, IFNULL(civicrm_country.name,'') as country
    @@ -307,18 +309,18 @@ public static function getPetitionSignatureTotalbyCountry($surveyId) {
                     civicrm_survey.id =  %1 AND
                     a.source_record_id =  %1  ";
     
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
    -    $params = array(
    -      1 => array($surveyId, 'Integer'),
    -      2 => array($sourceID, 'Integer'),
    -    );
    +    $params = [
    +      1 => [$surveyId, 'Integer'],
    +      2 => [$sourceID, 'Integer'],
    +    ];
         $sql .= " GROUP BY civicrm_address.country_id";
    -    $fields = array('total', 'country_id', 'country_iso', 'country');
    +    $fields = ['total', 'country_id', 'country_iso', 'country'];
     
         $dao = CRM_Core_DAO::executeQuery($sql, $params);
         while ($dao->fetch()) {
    -      $row = array();
    +      $row = [];
           foreach ($fields as $field) {
             $row[$field] = $dao->$field;
           }
    @@ -344,7 +346,7 @@ public static function getPetitionSignatureTotal($surveyId) {
                 WHERE
                 source_record_id = " . (int) $surveyId . " AND activity_type_id = " . (int) $surveyInfo['activity_type_id'] . " GROUP BY status_id";
     
    -    $statusTotal = array();
    +    $statusTotal = [];
         $total = 0;
         $dao = CRM_Core_DAO::executeQuery($sql);
         while ($dao->fetch()) {
    @@ -355,14 +357,13 @@ public static function getPetitionSignatureTotal($surveyId) {
         return $statusTotal;
       }
     
    -
       /**
        * @param int $surveyId
        *
        * @return array
        */
       public static function getSurveyInfo($surveyId = NULL) {
    -    $surveyInfo = array();
    +    $surveyInfo = [];
     
         $sql = "
                 SELECT  activity_type_id,
    @@ -399,7 +400,7 @@ public static function getPetitionSignature($surveyId, $status_id = NULL) {
     
         // sql injection protection
         $surveyId = (int) $surveyId;
    -    $signature = array();
    +    $signature = [];
     
         $sql = "
                 SELECT  a.id,
    @@ -424,19 +425,19 @@ public static function getPetitionSignature($surveyId, $status_id = NULL) {
                 civicrm_survey.id =  %1 AND
                 a.source_record_id =  %1 ";
     
    -    $params = array(1 => array($surveyId, 'Integer'));
    +    $params = [1 => [$surveyId, 'Integer']];
     
         if ($status_id) {
           $sql .= " AND status_id = %2";
    -      $params[2] = array($status_id, 'Integer');
    +      $params[2] = [$status_id, 'Integer'];
         }
         $sql .= " ORDER BY  a.activity_date_time";
     
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
    -    $params[3] = array($sourceID, 'Integer');
    +    $params[3] = [$sourceID, 'Integer'];
     
    -    $fields = array(
    +    $fields = [
           'id',
           'survey_id',
           'contact_id',
    @@ -451,11 +452,11 @@ public static function getPetitionSignature($surveyId, $status_id = NULL) {
           'state_province_id',
           'country_iso',
           'country',
    -    );
    +    ];
     
         $dao = CRM_Core_DAO::executeQuery($sql, $params);
         while ($dao->fetch()) {
    -      $row = array();
    +      $row = [];
           foreach ($fields as $field) {
             $row[$field] = $dao->$field;
           }
    @@ -474,7 +475,7 @@ public static function getPetitionSignature($surveyId, $status_id = NULL) {
        *   array of contact ids
        */
       public function getEntitiesByTag($tag) {
    -    $contactIds = array();
    +    $contactIds = [];
         $entityTagDAO = new CRM_Core_DAO_EntityTag();
         $entityTagDAO->tag_id = $tag['id'];
         $entityTagDAO->find();
    @@ -496,8 +497,8 @@ public function getEntitiesByTag($tag) {
       public static function checkSignature($surveyId, $contactId) {
     
         $surveyInfo = CRM_Campaign_BAO_Petition::getSurveyInfo($surveyId);
    -    $signature = array();
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $signature = [];
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
     
         $sql = "
    @@ -514,13 +515,13 @@ public static function checkSignature($surveyId, $contactId) {
                 AND a.activity_type_id = %3
                 AND ac.contact_id = %4
     ";
    -    $params = array(
    -      1 => array($surveyInfo['title'], 'String'),
    -      2 => array($surveyId, 'Integer'),
    -      3 => array($surveyInfo['activity_type_id'], 'Integer'),
    -      4 => array($contactId, 'Integer'),
    -      5 => array($sourceID, 'Integer'),
    -    );
    +    $params = [
    +      1 => [$surveyInfo['title'], 'String'],
    +      2 => [$surveyId, 'Integer'],
    +      3 => [$surveyInfo['activity_type_id'], 'Integer'],
    +      4 => [$contactId, 'Integer'],
    +      5 => [$sourceID, 'Integer'],
    +    ];
     
         $dao = CRM_Core_DAO::executeQuery($sql, $params);
         while ($dao->fetch()) {
    @@ -573,7 +574,7 @@ public static function sendEmail($params, $sendEmailMode) {
     
         // get petition info
         $petitionParams['id'] = $params['sid'];
    -    $petitionInfo = array();
    +    $petitionInfo = [];
         CRM_Campaign_BAO_Survey::retrieve($petitionParams, $petitionInfo);
         if (empty($petitionInfo)) {
           CRM_Core_Error::fatal('Petition doesn\'t exist.');
    @@ -586,7 +587,7 @@ public static function sendEmail($params, $sendEmailMode) {
     
         $toName = CRM_Contact_BAO_Contact::displayName($params['contactId']);
     
    -    $replyTo = "do-not-reply@$emailDomain";
    +    $replyTo = CRM_Core_BAO_Domain::getNoReplyEmailAddress();
     
         // set additional general message template params (custom tokens to use in email msg templates)
         // tokens then available in msg template as {$petition.title}, etc
    @@ -599,12 +600,12 @@ public static function sendEmail($params, $sendEmailMode) {
     
             // add this contact to the CIVICRM_PETITION_CONTACTS group
             // Cannot pass parameter 1 by reference
    -        $p = array($params['contactId']);
    +        $p = [$params['contactId']];
             CRM_Contact_BAO_GroupContact::addContactsToGroup($p, $group_id, 'API');
     
             if ($params['email-Primary']) {
               CRM_Core_BAO_MessageTemplate::sendTemplate(
    -            array(
    +            [
                   'groupName' => 'msg_tpl_workflow_petition',
                   'valueName' => 'petition_sign',
                   'contactId' => $params['contactId'],
    @@ -615,7 +616,7 @@ public static function sendEmail($params, $sendEmailMode) {
                   'replyTo' => $replyTo,
                   'petitionId' => $params['sid'],
                   'petitionTitle' => $petitionInfo['title'],
    -            )
    +            ]
               );
             }
             break;
    @@ -635,12 +636,12 @@ public static function sendEmail($params, $sendEmailMode) {
             $localpart = CRM_Core_BAO_MailSettings::defaultLocalpart();
     
             $replyTo = implode($config->verpSeparator,
    -            array(
    +            [
                   $localpart . 'c',
                   $se->contact_id,
                   $se->id,
                   $se->hash,
    -            )
    +            ]
               ) . "@$emailDomain";
     
             $confirmUrl = CRM_Utils_System::url('civicrm/petition/confirm',
    @@ -661,7 +662,7 @@ public static function sendEmail($params, $sendEmailMode) {
     
             if ($params['email-Primary']) {
               CRM_Core_BAO_MessageTemplate::sendTemplate(
    -            array(
    +            [
                   'groupName' => 'msg_tpl_workflow_petition',
                   'valueName' => 'petition_confirmation_needed',
                   'contactId' => $params['contactId'],
    @@ -673,7 +674,7 @@ public static function sendEmail($params, $sendEmailMode) {
                   'petitionId' => $params['sid'],
                   'petitionTitle' => $petitionInfo['title'],
                   'confirmUrl' => $confirmUrl,
    -            )
    +            ]
               );
             }
             break;
    diff --git a/CRM/Campaign/BAO/Query.php b/CRM/Campaign/BAO/Query.php
    index 6ccf34faf7c4..d44e12800994 100644
    --- a/CRM/Campaign/BAO/Query.php
    +++ b/CRM/Campaign/BAO/Query.php
    @@ -1,9 +1,9 @@
     _select) && $query->_mode == CRM_Contact_BAO_Query::MODE_CONTACTS) {
           foreach ($query->_select as $field => $queryString) {
             if (substr($field, -11) == 'campaign_id') {
    -          $query->_pseudoConstantsSelect[$field] = array(
    +          $query->_pseudoConstantsSelect[$field] = [
                 'pseudoField' => 'campaign_id',
                 'idCol' => $field,
                 'bao' => 'CRM_Activity_BAO_Activity',
    -          );
    +          ];
             }
           }
         }
    @@ -171,7 +171,7 @@ public static function whereClauseSingle(&$values, &$query) {
     
         switch ($name) {
           case 'campaign_survey_id':
    -        $query->_qill[$grouping][] = ts('Survey - %1', array(1 => CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $value, 'title')));
    +        $query->_qill[$grouping][] = ts('Survey - %1', [1 => CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $value, 'title')]);
     
             $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity.source_record_id',
               $op, $value, 'Integer'
    @@ -184,21 +184,21 @@ public static function whereClauseSingle(&$values, &$query) {
           case 'survey_status_id':
             $activityStatus = CRM_Core_PseudoConstant::activityStatus();
     
    -        $query->_qill[$grouping][] = ts('Survey Status - %1', array(1 => $activityStatus[$value]));
    +        $query->_qill[$grouping][] = ts('Survey Status - %1', [1 => $activityStatus[$value]]);
             $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity.status_id',
               $op, $value, 'Integer'
             );
             return;
     
           case 'campaign_search_voter_for':
    -        if (in_array($value, array('release', 'interview'))) {
    +        if (in_array($value, ['release', 'interview'])) {
               $query->_where[$grouping][] = '(civicrm_activity.is_deleted = 0 OR civicrm_activity.is_deleted IS NULL)';
             }
             return;
     
           case 'survey_interviewer_id':
             $surveyInterviewerName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'sort_name');
    -        $query->_qill[$grouping][] = ts('Survey Interviewer - %1', array(1 => $surveyInterviewerName));
    +        $query->_qill[$grouping][] = ts('Survey Interviewer - %1', [1 => $surveyInterviewerName]);
             $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_activity_assignment.contact_id',
               $op, $value, 'Integer'
             );
    @@ -221,7 +221,7 @@ public static function from($name, $mode, $side) {
           return $from;
         }
     
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
         $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
         $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
    @@ -269,7 +269,7 @@ public static function defaultReturnProperties(
       ) {
         $properties = NULL;
         if ($mode & CRM_Contact_BAO_Query::MODE_CAMPAIGN) {
    -      $properties = array(
    +      $properties = [
             'contact_id' => 1,
             'contact_type' => 1,
             'contact_sub_type' => 1,
    @@ -292,7 +292,7 @@ public static function defaultReturnProperties(
             'campaign_id' => 1,
             'survey_interviewer_id' => 1,
             'survey_activity_target_contact_id' => 1,
    -      );
    +      ];
         }
     
         return $properties;
    @@ -355,11 +355,11 @@ public static function buildSearchForm(&$form) {
         $separator = CRM_Core_DAO::VALUE_SEPARATOR;
         $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, TRUE, $separator);
         $form->add('select', 'contact_type', ts('Contact Type(s)'), $contactTypes, FALSE,
    -      array('id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2')
    +      ['id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2']
         );
         $groups = CRM_Core_PseudoConstant::nestedGroup();
         $form->add('select', 'group', ts('Groups'), $groups, FALSE,
    -      array('multiple' => 'multiple', 'class' => 'crm-select2')
    +      ['multiple' => 'multiple', 'class' => 'crm-select2']
         );
     
         $showInterviewer = FALSE;
    @@ -372,7 +372,7 @@ public static function buildSearchForm(&$form) {
           $className == 'CRM_Campaign_Form_Gotv'
         ) {
     
    -      $form->addEntityRef('survey_interviewer_id', ts('Interviewer'), array('class' => 'big'));
    +      $form->addEntityRef('survey_interviewer_id', ts('Interviewer'), ['class' => 'big']);
     
           $userId = NULL;
           if (isset($form->_interviewerId) && $form->_interviewerId) {
    @@ -383,7 +383,7 @@ public static function buildSearchForm(&$form) {
             $userId = $session->get('userID');
           }
           if ($userId) {
    -        $defaults = array();
    +        $defaults = [];
             $defaults['survey_interviewer_id'] = $userId;
             $form->setDefaults($defaults);
           }
    @@ -395,13 +395,10 @@ public static function buildSearchForm(&$form) {
           FROM  civicrm_custom_field fld
     INNER JOIN  civicrm_custom_group grp on fld.custom_group_id = grp.id
          WHERE  grp.name = %1';
    -    $dao = CRM_Core_DAO::executeQuery($query, array(1 => array('Voter_Info', 'String')));
    -    $customSearchFields = array();
    +    $dao = CRM_Core_DAO::executeQuery($query, [1 => ['Voter_Info', 'String']]);
    +    $customSearchFields = [];
         while ($dao->fetch()) {
    -      foreach (array(
    -                 'ward',
    -                 'precinct',
    -               ) as $name) {
    +      foreach (['ward', 'precinct'] as $name) {
             if (stripos($name, $dao->label) !== FALSE) {
               $fieldId = $dao->id;
               $fieldName = 'custom_' . $dao->id;
    @@ -419,7 +416,7 @@ public static function buildSearchForm(&$form) {
           ($className == 'CRM_Campaign_Form_Search')
         ) {
           CRM_Core_Error::statusBounce(ts('Could not find survey for %1 respondents.',
    -          array(1 => $form->get('op'))
    +          [1 => $form->get('op')]
             ),
             CRM_Utils_System::url('civicrm/survey/add',
               'reset=1&action=add'
    @@ -432,7 +429,7 @@ public static function buildSearchForm(&$form) {
         //campaign has some contact groups, don't
         //allow to search the contacts those are not
         //in given campaign groups ( ie not in constituents )
    -    $props = array('class' => 'crm-select2');
    +    $props = ['class' => 'crm-select2'];
         if ($form->get('searchVoterFor') == 'reserve') {
           $props['onChange'] = "buildCampaignGroups( );return false;";
         }
    @@ -447,13 +444,14 @@ public static function buildSearchForm(&$form) {
        *   An array.
        * @return $voterClause as a string
        */
    +
       /**
        * @param array $params
        *
        * @return array
        */
    -  static public function voterClause($params) {
    -    $voterClause = array();
    +  public static function voterClause($params) {
    +    $voterClause = [];
         $fromClause = $whereClause = NULL;
         if (!is_array($params) || empty($params)) {
           return $voterClause;
    @@ -463,7 +461,7 @@ static public function voterClause($params) {
     
         //get the survey activities.
         $activityStatus = CRM_Core_PseudoConstant::activityStatus('name');
    -    $status = array('Scheduled');
    +    $status = ['Scheduled'];
         if ($searchVoterFor == 'reserve') {
           $status[] = 'Completed';
         }
    @@ -494,7 +492,7 @@ static public function voterClause($params) {
               is_array($recontactInterval) &&
               !empty($recontactInterval)
             ) {
    -          $voterIds = array();
    +          $voterIds = [];
               foreach ($voterActValues as $values) {
                 $numOfDays = CRM_Utils_Array::value($values['result'], $recontactInterval);
                 if ($numOfDays &&
    @@ -554,10 +552,10 @@ static public function voterClause($params) {
             }
           }
         }
    -    $voterClause = array(
    +    $voterClause = [
           'fromClause' => $fromClause,
           'whereClause' => $whereClause,
    -    );
    +    ];
     
         return $voterClause;
       }
    diff --git a/CRM/Campaign/BAO/Survey.php b/CRM/Campaign/BAO/Survey.php
    index dfc855e62108..4333c0111888 100644
    --- a/CRM/Campaign/BAO/Survey.php
    +++ b/CRM/Campaign/BAO/Survey.php
    @@ -1,9 +1,9 @@
     copyValues($params);
         $dao->save();
     
    +    if (!empty($params['id'])) {
    +      CRM_Utils_Hook::post('edit', 'Survey', $dao->id, $dao);
    +    }
    +    else {
    +      CRM_Utils_Hook::post('create', 'Survey', $dao->id, $dao);
    +    }
    +
         if (!empty($params['custom']) &&
           is_array($params['custom'])
         ) {
    @@ -112,16 +121,16 @@ public static function create(&$params) {
        *
        * @return array|int
        */
    -  public static function getSurveySummary($params = array(), $onlyCount = FALSE) {
    +  public static function getSurveySummary($params = [], $onlyCount = FALSE) {
         //build the limit and order clause.
         $limitClause = $orderByClause = $lookupTableJoins = NULL;
         if (!$onlyCount) {
    -      $sortParams = array(
    +      $sortParams = [
             'sort' => 'created_date',
             'offset' => 0,
             'rowCount' => 10,
             'sortOrder' => 'desc',
    -      );
    +      ];
           foreach ($sortParams as $name => $default) {
             if (!empty($params[$name])) {
               $sortParams[$name] = $params[$name];
    @@ -154,23 +163,23 @@ public static function getSurveySummary($params = array(), $onlyCount = FALSE) {
         }
     
         //build the where clause.
    -    $queryParams = $where = array();
    +    $queryParams = $where = [];
     
         //we only have activity type as a
         //difference between survey and petition.
    -    $petitionTypeID = CRM_Core_OptionGroup::getValue('activity_type', 'petition', 'name');
    +    $petitionTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Petition');
         if ($petitionTypeID) {
           $where[] = "( survey.activity_type_id != %1 )";
    -      $queryParams[1] = array($petitionTypeID, 'Positive');
    +      $queryParams[1] = [$petitionTypeID, 'Positive'];
         }
     
         if (!empty($params['title'])) {
           $where[] = "( survey.title LIKE %2 )";
    -      $queryParams[2] = array('%' . trim($params['title']) . '%', 'String');
    +      $queryParams[2] = ['%' . trim($params['title']) . '%', 'String'];
         }
         if (!empty($params['campaign_id'])) {
           $where[] = '( survey.campaign_id = %3 )';
    -      $queryParams[3] = array($params['campaign_id'], 'Positive');
    +      $queryParams[3] = [$params['campaign_id'], 'Positive'];
         }
         if (!empty($params['activity_type_id'])) {
           $typeId = $params['activity_type_id'];
    @@ -207,8 +216,8 @@ public static function getSurveySummary($params = array(), $onlyCount = FALSE) {
           return (int) CRM_Core_DAO::singleValueQuery($query, $queryParams);
         }
     
    -    $surveys = array();
    -    $properties = array(
    +    $surveys = [];
    +    $properties = [
           'id',
           'title',
           'campaign_id',
    @@ -219,7 +228,7 @@ public static function getSurveySummary($params = array(), $onlyCount = FALSE) {
           'release_frequency',
           'max_number_of_contacts',
           'default_number_of_contacts',
    -    );
    +    ];
     
         $survey = CRM_Core_DAO::executeQuery($query, $queryParams);
         while ($survey->fetch()) {
    @@ -254,7 +263,7 @@ public static function getSurveyCount() {
        */
       public static function getSurveys($onlyActive = TRUE, $onlyDefault = FALSE, $forceAll = FALSE, $includePetition = FALSE) {
         $cacheKey = 0;
    -    $cacheKeyParams = array('onlyActive', 'onlyDefault', 'forceAll', 'includePetition');
    +    $cacheKeyParams = ['onlyActive', 'onlyDefault', 'forceAll', 'includePetition'];
         foreach ($cacheKeyParams as $param) {
           $cacheParam = $$param;
           if (!$cacheParam) {
    @@ -271,7 +280,7 @@ public static function getSurveys($onlyActive = TRUE, $onlyDefault = FALSE, $for
             //difference between survey and petition.
             $petitionTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'petition');
     
    -        $where = array();
    +        $where = [];
             if ($petitionTypeID) {
               $where[] = "( survey.activity_type_id != {$petitionTypeID} )";
             }
    @@ -289,7 +298,7 @@ public static function getSurveys($onlyActive = TRUE, $onlyDefault = FALSE, $for
             survey.title as title
       FROM  civicrm_survey as survey
      WHERE  {$whereClause}";
    -      $surveys[$cacheKey] = array();
    +      $surveys[$cacheKey] = [];
           $survey = CRM_Core_DAO::executeQuery($query);
           while ($survey->fetch()) {
             $surveys[$cacheKey][$survey->id] = $survey->title;
    @@ -305,14 +314,14 @@ public static function getSurveys($onlyActive = TRUE, $onlyDefault = FALSE, $for
        * @param string $returnColumn
        * @param bool $includePetitionActivityType
        *
    -   * @return string
    +   * @return mixed
        */
       public static function getSurveyActivityType($returnColumn = 'label', $includePetitionActivityType = FALSE) {
         static $activityTypes;
         $cacheKey = "{$returnColumn}_{$includePetitionActivityType}";
     
         if (!isset($activityTypes[$cacheKey])) {
    -      $activityTypes = array();
    +      $activityTypes = [];
           $campaignCompId = CRM_Core_Component::getComponentID('CiviCampaign');
           if ($campaignCompId) {
             $condition = " AND v.component_id={$campaignCompId}";
    @@ -342,10 +351,10 @@ public static function getSurveyActivityType($returnColumn = 'label', $includePe
        *
        * @return array
        */
    -  public static function getSurveyCustomGroups($surveyTypes = array()) {
    -    $customGroups = array();
    +  public static function getSurveyCustomGroups($surveyTypes = []) {
    +    $customGroups = [];
         if (!is_array($surveyTypes)) {
    -      $surveyTypes = array($surveyTypes);
    +      $surveyTypes = [$surveyTypes];
         }
     
         if (!empty($surveyTypes)) {
    @@ -382,8 +391,8 @@ public static function getSurveyCustomGroups($surveyTypes = array()) {
        * @param bool $is_active
        *   Value we want to set the is_active field.
        *
    -   * @return Object
    -   *   DAO object on success, null otherwise
    +   * @return bool
    +   *   true if we found and updated the object, else false
        */
       public static function setIsActive($id, $is_active) {
         return CRM_Core_DAO::setFieldValue('CRM_Campaign_DAO_Survey', $id, 'is_active', $is_active);
    @@ -420,8 +429,8 @@ public static function del($id) {
        * @return array
        *   array of contact info.
        */
    -  public static function voterDetails($voterIds, $returnProperties = array()) {
    -    $voterDetails = array();
    +  public static function voterDetails($voterIds, $returnProperties = []) {
    +    $voterDetails = [];
         if (!is_array($voterIds) || empty($voterIds)) {
           return $voterDetails;
         }
    @@ -430,21 +439,18 @@ public static function voterDetails($voterIds, $returnProperties = array()) {
           $autocompleteContactSearch = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
             'contact_autocomplete_options'
           );
    -      $returnProperties = array_fill_keys(array_merge(array(
    -          'contact_type',
    -          'contact_sub_type',
    -          'sort_name',
    -        ),
    +      $returnProperties = array_fill_keys(array_merge(
    +        ['contact_type', 'contact_sub_type', 'sort_name'],
             array_keys($autocompleteContactSearch)
           ), 1);
         }
     
    -    $select = $from = array();
    +    $select = $from = [];
         foreach ($returnProperties as $property => $ignore) {
    -      $value = (in_array($property, array(
    +      $value = (in_array($property, [
             'city',
             'street_address',
    -      ))) ? 'address' : $property;
    +      ])) ? 'address' : $property;
           switch ($property) {
             case 'sort_name':
             case 'contact_type':
    @@ -495,7 +501,6 @@ public static function voterDetails($voterIds, $returnProperties = array()) {
             );
             $voterDetails[$contact->contactId]['contact_type'] = $image;
           }
    -      $contact->free();
         }
     
         return $voterDetails;
    @@ -515,8 +520,8 @@ public static function voterDetails($voterIds, $returnProperties = array()) {
        * @return array
        *   array of survey activity.
        */
    -  public static function voterActivityDetails($surveyId, $voterIds, $interviewerId = NULL, $statusIds = array()) {
    -    $activityDetails = array();
    +  public static function voterActivityDetails($surveyId, $voterIds, $interviewerId = NULL, $statusIds = []) {
    +    $activityDetails = [];
         if (!$surveyId ||
           !is_array($voterIds) || empty($voterIds)
         ) {
    @@ -529,11 +534,11 @@ public static function voterActivityDetails($surveyId, $voterIds, $interviewerId
         }
     
         $targetContactIds = ' ( ' . implode(',', $voterIds) . ' ) ';
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
         $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
     
    -    $params[1] = array($surveyId, 'Integer');
    +    $params[1] = [$surveyId, 'Integer'];
         $query = "
         SELECT  activity.id, activity.status_id,
                 activityTarget.contact_id as voter_id,
    @@ -547,18 +552,18 @@ public static function voterActivityDetails($surveyId, $voterIds, $interviewerId
          AND  ( activity.is_deleted IS NULL OR activity.is_deleted = 0 ) ";
         if (!empty($interviewerId)) {
           $query .= "AND activityAssignment.contact_id = %2 ";
    -      $params[2] = array($interviewerId, 'Integer');
    +      $params[2] = [$interviewerId, 'Integer'];
         }
         $query .= "AND  activityTarget.contact_id IN {$targetContactIds}
                 $whereClause";
         $activity = CRM_Core_DAO::executeQuery($query, $params);
         while ($activity->fetch()) {
    -      $activityDetails[$activity->voter_id] = array(
    +      $activityDetails[$activity->voter_id] = [
             'voter_id' => $activity->voter_id,
             'status_id' => $activity->status_id,
             'activity_id' => $activity->id,
             'interviewer_id' => $activity->interviewer_id,
    -      );
    +      ];
         }
     
         return $activityDetails;
    @@ -583,13 +588,13 @@ public static function getSurveyActivities(
         $voterIds = NULL,
         $onlyCount = FALSE
       ) {
    -    $activities = array();
    +    $activities = [];
         $surveyActivityCount = 0;
         if (!$surveyId) {
           return ($onlyCount) ? 0 : $activities;
         }
     
    -    $where = array();
    +    $where = [];
         if (!empty($statusIds)) {
           $where[] = '( activity.status_id IN ( ' . implode(',', array_values($statusIds)) . ' ) )';
         }
    @@ -625,7 +630,7 @@ public static function getSurveyActivities(
                 contact_a.display_name as voter_name";
         }
     
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
         $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
     
    @@ -642,10 +647,10 @@ public static function getSurveyActivities(
            AND  ( activity.is_deleted IS NULL OR activity.is_deleted = 0 )
                 $whereClause";
     
    -    $params = array(
    -      1 => array($surveyId, 'Integer'),
    -      2 => array($actTypeId, 'Integer'),
    -    );
    +    $params = [
    +      1 => [$surveyId, 'Integer'],
    +      2 => [$actTypeId, 'Integer'],
    +    ];
     
         if ($onlyCount) {
           $dbCount = CRM_Core_DAO::singleValueQuery($query, $params);
    @@ -655,7 +660,7 @@ public static function getSurveyActivities(
         $activity = CRM_Core_DAO::executeQuery($query, $params);
     
         while ($activity->fetch()) {
    -      $activities[$activity->id] = array(
    +      $activities[$activity->id] = [
             'id' => $activity->id,
             'voter_id' => $activity->voter_id,
             'voter_name' => $activity->voter_name,
    @@ -663,7 +668,7 @@ public static function getSurveyActivities(
             'interviewer_id' => $activity->interviewer_id,
             'result' => $activity->result,
             'activity_date_time' => $activity->activity_date_time,
    -      );
    +      ];
         }
     
         return $activities;
    @@ -682,8 +687,8 @@ public static function getSurveyActivities(
        * @return array
        *   Survey related contact ids.
        */
    -  public static function getSurveyVoterInfo($surveyId, $interviewerId = NULL, $statusIds = array()) {
    -    $voterIds = array();
    +  public static function getSurveyVoterInfo($surveyId, $interviewerId = NULL, $statusIds = []) {
    +    $voterIds = [];
         if (!$surveyId) {
           return $voterIds;
         }
    @@ -696,7 +701,7 @@ public static function getSurveyVoterInfo($surveyId, $interviewerId = NULL, $sta
           $cacheKey = "{$cacheKey}_" . implode('_', $statusIds);
         }
     
    -    static $contactIds = array();
    +    static $contactIds = [];
         if (!isset($contactIds[$cacheKey])) {
           $activities = self::getSurveyActivities($surveyId, $interviewerId, $statusIds);
           foreach ($activities as $values) {
    @@ -716,7 +721,7 @@ public static function getSurveyVoterInfo($surveyId, $interviewerId = NULL, $sta
        *   an array of option groups.
        */
       public static function getResultSets($valueColumnName = 'title') {
    -    $resultSets = array();
    +    $resultSets = [];
         $valueColumnName = CRM_Utils_Type::escape($valueColumnName, 'String');
     
         $query = "SELECT id, {$valueColumnName} FROM civicrm_option_group WHERE name LIKE 'civicrm_survey_%' AND is_active=1";
    @@ -765,7 +770,7 @@ public static function isSurveyActivity($activityId) {
        *   an array of option values
        */
       public static function getResponsesOptions($surveyId) {
    -    $responseOptions = array();
    +    $responseOptions = [];
         if (!$surveyId) {
           return $responseOptions;
         }
    @@ -788,12 +793,12 @@ public static function getResponsesOptions($surveyId) {
        *   $url array of permissioned links
        */
       public static function buildPermissionLinks($surveyId, $enclosedInUL = FALSE, $extraULName = 'more') {
    -    $menuLinks = array();
    +    $menuLinks = [];
         if (!$surveyId) {
           return $menuLinks;
         }
     
    -    static $voterLinks = array();
    +    static $voterLinks = [];
         if (empty($voterLinks)) {
           $permissioned = FALSE;
           if (CRM_Core_Permission::check('manage campaign') ||
    @@ -803,44 +808,44 @@ public static function buildPermissionLinks($surveyId, $enclosedInUL = FALSE, $e
           }
     
           if ($permissioned || CRM_Core_Permission::check("reserve campaign contacts")) {
    -        $voterLinks['reserve'] = array(
    +        $voterLinks['reserve'] = [
               'name' => 'reserve',
               'url' => 'civicrm/survey/search',
               'qs' => 'sid=%%id%%&reset=1&op=reserve',
               'title' => ts('Reserve Respondents'),
    -        );
    +        ];
           }
           if ($permissioned || CRM_Core_Permission::check("interview campaign contacts")) {
    -        $voterLinks['release'] = array(
    +        $voterLinks['release'] = [
               'name' => 'interview',
               'url' => 'civicrm/survey/search',
               'qs' => 'sid=%%id%%&reset=1&op=interview&force=1',
               'title' => ts('Interview Respondents'),
    -        );
    +        ];
           }
           if ($permissioned || CRM_Core_Permission::check("release campaign contacts")) {
    -        $voterLinks['interview'] = array(
    +        $voterLinks['interview'] = [
               'name' => 'release',
               'url' => 'civicrm/survey/search',
               'qs' => 'sid=%%id%%&reset=1&op=release&force=1',
               'title' => ts('Release Respondents'),
    -        );
    +        ];
           }
         }
     
         if (CRM_Core_Permission::check('access CiviReport')) {
           $reportID = self::getReportID($surveyId);
           if ($reportID) {
    -        $voterLinks['report'] = array(
    +        $voterLinks['report'] = [
               'name' => 'report',
               'url' => "civicrm/report/instance/{$reportID}",
               'qs' => 'reset=1',
               'title' => ts('View Survey Report'),
    -        );
    +        ];
           }
         }
     
    -    $ids = array('id' => $surveyId);
    +    $ids = ['id' => $surveyId];
         foreach ($voterLinks as $link) {
           if (!empty($link['qs']) &&
             !CRM_Utils_System::isNull($link['qs'])
    @@ -878,19 +883,19 @@ public static function getSurveyProfileId($surveyId) {
           return NULL;
         }
     
    -    static $ufIds = array();
    +    static $ufIds = [];
         if (!array_key_exists($surveyId, $ufIds)) {
           //get the profile id.
    -      $ufJoinParams = array(
    +      $ufJoinParams = [
             'entity_id' => $surveyId,
             'entity_table' => 'civicrm_survey',
             'module' => 'CiviCampaign',
    -      );
    +      ];
     
           list($first, $second) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams);
     
           if ($first) {
    -        $ufIds[$surveyId] = array($first);
    +        $ufIds[$surveyId] = [$first];
           }
           if ($second) {
             $ufIds[$surveyId][] = array_shift($second);
    @@ -905,12 +910,12 @@ public static function getSurveyProfileId($surveyId) {
        *
        * @return mixed
        */
    -  public Static function getReportID($surveyId) {
    -    static $reportIds = array();
    +  public static function getReportID($surveyId) {
    +    static $reportIds = [];
     
         if (!array_key_exists($surveyId, $reportIds)) {
           $query = "SELECT MAX(id) as id FROM civicrm_report_instance WHERE name = %1";
    -      $reportID = CRM_Core_DAO::singleValueQuery($query, array(1 => array("survey_{$surveyId}", 'String')));
    +      $reportID = CRM_Core_DAO::singleValueQuery($query, [1 => ["survey_{$surveyId}", 'String']]);
           $reportIds[$surveyId] = $reportID;
         }
         return $reportIds[$surveyId];
    @@ -945,8 +950,8 @@ public static function surveyProfileTypes() {
         static $profileTypes;
     
         if (!isset($profileTypes)) {
    -      $profileTypes = array_merge(array('Activity', 'Contact'), CRM_Contact_BAO_ContactType::basicTypes());
    -      $profileTypes = array_diff($profileTypes, array('Organization', 'Household'));
    +      $profileTypes = array_merge(['Activity', 'Contact'], CRM_Contact_BAO_ContactType::basicTypes());
    +      $profileTypes = array_diff($profileTypes, ['Organization', 'Household']);
         }
     
         return $profileTypes;
    @@ -966,7 +971,7 @@ public static function surveyProfileTypes() {
        */
       public static function getSurveyResponseFields($surveyId, $surveyTypeId = NULL) {
         if (empty($surveyId)) {
    -      return array();
    +      return [];
         }
     
         static $responseFields;
    @@ -976,7 +981,7 @@ public static function getSurveyResponseFields($surveyId, $surveyTypeId = NULL)
           return $responseFields[$cacheKey];
         }
     
    -    $responseFields[$cacheKey] = array();
    +    $responseFields[$cacheKey] = [];
     
         $profileId = self::getSurveyProfileId($surveyId);
     
    @@ -993,7 +998,7 @@ public static function getSurveyResponseFields($surveyId, $surveyTypeId = NULL)
         );
     
         //don't load these fields in grid.
    -    $removeFields = array('File', 'RichTextEditor');
    +    $removeFields = ['File', 'RichTextEditor'];
     
         $supportableFieldTypes = self::surveyProfileTypes();
     
    @@ -1045,8 +1050,8 @@ public static function getInterviewers() {
           $whereClause = ' WHERE survey.activity_type_id IN ( ' . implode(' , ', array_keys($activityTypes)) . ' )';
         }
     
    -    $interviewers = array();
    -    $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
    +    $interviewers = [];
    +    $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
         $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
     
         $query = "
    @@ -1095,8 +1100,8 @@ public static function releaseRespondent($params) {
          WHERE  activity.is_deleted = 0
            AND  activity.status_id = %1
            AND  activity.activity_type_id IN ( ' . implode(', ', $surveyActivityTypesIds) . ' )';
    -      $activity = CRM_Core_DAO::executeQuery($query, array(1 => array($reserveStatusId, 'Positive')));
    -      $releasedIds = array();
    +      $activity = CRM_Core_DAO::executeQuery($query, [1 => [$reserveStatusId, 'Positive']]);
    +      $releasedIds = [];
           while ($activity->fetch()) {
             if (!$activity->release_frequency) {
               continue;
    @@ -1120,10 +1125,10 @@ public static function releaseRespondent($params) {
           }
         }
     
    -    $rtnMsg = array(
    +    $rtnMsg = [
           'is_error' => 0,
           'messages' => "Number of respondents released = {$releasedCount}",
    -    );
    +    ];
     
         return $rtnMsg;
       }
    @@ -1138,14 +1143,14 @@ public static function releaseRespondent($params) {
        *
        * @return array|bool
        */
    -  public static function buildOptions($fieldName, $context = NULL, $props = array()) {
    -    $params = array();
    +  public static function buildOptions($fieldName, $context = NULL, $props = []) {
    +    $params = [];
         // Special logic for fields whose options depend on context or properties
         switch ($fieldName) {
           case 'activity_type_id':
             $campaignCompId = CRM_Core_Component::getComponentID('CiviCampaign');
             if ($campaignCompId) {
    -          $params['condition'] = array("component_id={$campaignCompId}");
    +          $params['condition'] = ["component_id={$campaignCompId}"];
             }
             break;
         }
    diff --git a/CRM/Campaign/Controller/Search.php b/CRM/Campaign/Controller/Search.php
    index b306e52957d5..5f3740844066 100644
    --- a/CRM/Campaign/Controller/Search.php
    +++ b/CRM/Campaign/Controller/Search.php
    @@ -1,9 +1,9 @@
     __table = 'civicrm_campaign';
         parent::__construct();
       }
    +
       /**
        * Returns foreign keys and entity references.
        *
        * @return array
        *   [CRM_Core_Reference_Interface]
        */
    -  static function getReferenceColumns() {
    +  public static function getReferenceColumns() {
         if (!isset(Civi::$statics[__CLASS__]['links'])) {
    -      Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__);
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'parent_id', 'civicrm_campaign', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'created_id', 'civicrm_contact', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'last_modified_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'parent_id', 'civicrm_campaign', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'created_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'last_modified_id', 'civicrm_contact', 'id');
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
         }
         return Civi::$statics[__CLASS__]['links'];
       }
    +
       /**
        * Returns all the column names of this table
        *
        * @return array
        */
    -  static function &fields() {
    +  public static function &fields() {
         if (!isset(Civi::$statics[__CLASS__]['fields'])) {
    -      Civi::$statics[__CLASS__]['fields'] = array(
    -        'id' => array(
    +      Civi::$statics[__CLASS__]['fields'] = [
    +        'id' => [
               'name' => 'id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign ID') ,
    -          'description' => 'Unique Campaign ID.',
    -          'required' => true,
    -          'import' => true,
    +          'title' => ts('Campaign ID'),
    +          'description' => ts('Unique Campaign ID.'),
    +          'required' => TRUE,
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.id',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -        ) ,
    -        'name' => array(
    +        ],
    +        'name' => [
               'name' => 'name',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Campaign Name') ,
    -          'description' => 'Name of the Campaign.',
    -          'required' => true,
    +          'title' => ts('Campaign Name'),
    +          'description' => ts('Name of the Campaign.'),
    +          'required' => TRUE,
               'maxlength' => 255,
               'size' => CRM_Utils_Type::HUGE,
    -          'import' => true,
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.name',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'title' => array(
    +          ],
    +        ],
    +        'title' => [
               'name' => 'title',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Campaign Title') ,
    -          'description' => 'Title of the Campaign.',
    +          'title' => ts('Campaign Title'),
    +          'description' => ts('Title of the Campaign.'),
               'maxlength' => 255,
               'size' => CRM_Utils_Type::HUGE,
    -          'import' => true,
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.title',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'description' => array(
    +          ],
    +        ],
    +        'description' => [
               'name' => 'description',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Campaign Description') ,
    -          'description' => 'Full description of Campaign.',
    +          'title' => ts('Campaign Description'),
    +          'description' => ts('Full description of Campaign.'),
               'rows' => 8,
               'cols' => 60,
    +          'where' => 'civicrm_campaign.description',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'TextArea',
    -          ) ,
    -        ) ,
    -        'start_date' => array(
    +          ],
    +        ],
    +        'start_date' => [
               'name' => 'start_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Campaign Start Date') ,
    -          'description' => 'Date and time that Campaign starts.',
    -          'import' => true,
    +          'title' => ts('Campaign Start Date'),
    +          'description' => ts('Date and time that Campaign starts.'),
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.start_date',
               'headerPattern' => '/^start|(s(tart\s)?date)$/i',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select Date',
    -          ) ,
    -        ) ,
    -        'end_date' => array(
    +          ],
    +        ],
    +        'end_date' => [
               'name' => 'end_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Campaign End Date') ,
    -          'description' => 'Date and time that Campaign ends.',
    -          'import' => true,
    +          'title' => ts('Campaign End Date'),
    +          'description' => ts('Date and time that Campaign ends.'),
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.end_date',
               'headerPattern' => '/^end|(e(nd\s)?date)$/i',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select Date',
    -          ) ,
    -        ) ,
    -        'campaign_type_id' => array(
    +          ],
    +        ],
    +        'campaign_type_id' => [
               'name' => 'campaign_type_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign Type') ,
    -          'description' => 'Campaign Type ID.Implicit FK to civicrm_option_value where option_group = campaign_type',
    -          'import' => true,
    +          'title' => ts('Campaign Type'),
    +          'description' => ts('Campaign Type ID.Implicit FK to civicrm_option_value where option_group = campaign_type'),
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.campaign_type_id',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'campaign_type',
                 'optionEditPath' => 'civicrm/admin/options/campaign_type',
    -          )
    -        ) ,
    -        'status_id' => array(
    +          ],
    +        ],
    +        'status_id' => [
               'name' => 'status_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign Status') ,
    -          'description' => 'Campaign status ID.Implicit FK to civicrm_option_value where option_group = campaign_status',
    -          'import' => true,
    +          'title' => ts('Campaign Status'),
    +          'description' => ts('Campaign status ID.Implicit FK to civicrm_option_value where option_group = campaign_status'),
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.status_id',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'campaign_status',
                 'optionEditPath' => 'civicrm/admin/options/campaign_status',
    -          )
    -        ) ,
    -        'external_identifier' => array(
    +          ],
    +        ],
    +        'external_identifier' => [
               'name' => 'external_identifier',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Campaign External ID') ,
    -          'description' => 'Unique trusted external ID (generally from a legacy app/datasource). Particularly useful for deduping operations.',
    +          'title' => ts('Campaign External ID'),
    +          'description' => ts('Unique trusted external ID (generally from a legacy app/datasource). Particularly useful for deduping operations.'),
               'maxlength' => 32,
               'size' => CRM_Utils_Type::MEDIUM,
    -          'import' => true,
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.external_identifier',
               'headerPattern' => '/external\s?id/i',
               'dataPattern' => '/^\d{11,}$/',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -        'parent_id' => array(
    +          ],
    +        ],
    +        'parent_id' => [
               'name' => 'parent_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Parent Campaign') ,
    -          'description' => 'Optional parent id for this Campaign.',
    -          'import' => true,
    +          'title' => ts('Parent Campaign'),
    +          'description' => ts('Optional parent id for this Campaign.'),
    +          'import' => TRUE,
               'where' => 'civicrm_campaign.parent_id',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
               'FKClassName' => 'CRM_Campaign_DAO_Campaign',
    -          'html' => array(
    +          'html' => [
                 'type' => 'EntityRef',
    -          ) ,
    -        ) ,
    -        'is_active' => array(
    +          ],
    +        ],
    +        'is_active' => [
               'name' => 'is_active',
               'type' => CRM_Utils_Type::T_BOOLEAN,
    -          'title' => ts('Is Campaign Active?') ,
    -          'description' => 'Is this Campaign enabled or disabled/cancelled?',
    +          'title' => ts('Is Campaign Active?'),
    +          'description' => ts('Is this Campaign enabled or disabled/cancelled?'),
    +          'where' => 'civicrm_campaign.is_active',
               'default' => '1',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'CheckBox',
    -          ) ,
    -        ) ,
    -        'created_id' => array(
    +          ],
    +        ],
    +        'created_id' => [
               'name' => 'created_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign Created By') ,
    -          'description' => 'FK to civicrm_contact, who created this Campaign.',
    +          'title' => ts('Campaign Created By'),
    +          'description' => ts('FK to civicrm_contact, who created this Campaign.'),
    +          'where' => 'civicrm_campaign.created_id',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'created_date' => array(
    +        ],
    +        'created_date' => [
               'name' => 'created_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Campaign Created Date') ,
    -          'description' => 'Date and time that Campaign was created.',
    +          'title' => ts('Campaign Created Date'),
    +          'description' => ts('Date and time that Campaign was created.'),
    +          'where' => 'civicrm_campaign.created_date',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select Date',
    -          ) ,
    -        ) ,
    -        'last_modified_id' => array(
    +          ],
    +        ],
    +        'last_modified_id' => [
               'name' => 'last_modified_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign Modified By') ,
    -          'description' => 'FK to civicrm_contact, who recently edited this Campaign.',
    +          'title' => ts('Campaign Modified By'),
    +          'description' => ts('FK to civicrm_contact, who recently edited this Campaign.'),
    +          'where' => 'civicrm_campaign.last_modified_id',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'last_modified_date' => array(
    +        ],
    +        'last_modified_date' => [
               'name' => 'last_modified_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Campaign Modified Date') ,
    -          'description' => 'Date and time that Campaign was edited last time.',
    +          'title' => ts('Campaign Modified Date'),
    +          'description' => ts('Date and time that Campaign was edited last time.'),
    +          'where' => 'civicrm_campaign.last_modified_date',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -        ) ,
    -        'goal_general' => array(
    +        ],
    +        'goal_general' => [
               'name' => 'goal_general',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Campaign Goals') ,
    -          'description' => 'General goals for Campaign.',
    +          'title' => ts('Campaign Goals'),
    +          'description' => ts('General goals for Campaign.'),
    +          'where' => 'civicrm_campaign.goal_general',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'RichTextEditor',
    -          ) ,
    -        ) ,
    -        'goal_revenue' => array(
    +          ],
    +        ],
    +        'goal_revenue' => [
               'name' => 'goal_revenue',
               'type' => CRM_Utils_Type::T_MONEY,
    -          'title' => ts('Goal Revenue') ,
    -          'description' => 'The target revenue for this campaign.',
    -          'precision' => array(
    +          'title' => ts('Goal Revenue'),
    +          'description' => ts('The target revenue for this campaign.'),
    +          'precision' => [
                 20,
    -            2
    -          ) ,
    +            2,
    +          ],
    +          'where' => 'civicrm_campaign.goal_revenue',
               'table_name' => 'civicrm_campaign',
               'entity' => 'Campaign',
               'bao' => 'CRM_Campaign_BAO_Campaign',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Text',
    -          ) ,
    -        ) ,
    -      );
    +          ],
    +        ],
    +      ];
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
         }
         return Civi::$statics[__CLASS__]['fields'];
       }
    +
       /**
        * Return a mapping from field-name to the corresponding key (as used in fields()).
        *
        * @return array
        *   Array(string $name => string $uniqueName).
        */
    -  static function &fieldKeys() {
    +  public static function &fieldKeys() {
         if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
           Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
         }
         return Civi::$statics[__CLASS__]['fieldKeys'];
       }
    +
       /**
        * Returns the names of this table
        *
        * @return string
        */
    -  static function getTableName() {
    +  public static function getTableName() {
         return self::$_tableName;
       }
    +
       /**
        * Returns if this table needs to be logged
        *
    -   * @return boolean
    +   * @return bool
        */
    -  function getLog() {
    +  public function getLog() {
         return self::$_log;
       }
    +
       /**
        * Returns the list of fields that can be imported
        *
    @@ -506,10 +501,11 @@ function getLog() {
        *
        * @return array
        */
    -  static function &import($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'campaign', $prefix, array());
    +  public static function &import($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'campaign', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of fields that can be exported
        *
    @@ -517,41 +513,47 @@ static function &import($prefix = false) {
        *
        * @return array
        */
    -  static function &export($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'campaign', $prefix, array());
    +  public static function &export($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'campaign', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of indices
    +   *
    +   * @param bool $localize
    +   *
    +   * @return array
        */
       public static function indices($localize = TRUE) {
    -    $indices = array(
    -      'UI_campaign_type_id' => array(
    +    $indices = [
    +      'UI_campaign_type_id' => [
             'name' => 'UI_campaign_type_id',
    -        'field' => array(
    +        'field' => [
               0 => 'campaign_type_id',
    -        ) ,
    -        'localizable' => false,
    +        ],
    +        'localizable' => FALSE,
             'sig' => 'civicrm_campaign::0::campaign_type_id',
    -      ) ,
    -      'UI_campaign_status_id' => array(
    +      ],
    +      'UI_campaign_status_id' => [
             'name' => 'UI_campaign_status_id',
    -        'field' => array(
    +        'field' => [
               0 => 'status_id',
    -        ) ,
    -        'localizable' => false,
    +        ],
    +        'localizable' => FALSE,
             'sig' => 'civicrm_campaign::0::status_id',
    -      ) ,
    -      'UI_external_identifier' => array(
    +      ],
    +      'UI_external_identifier' => [
             'name' => 'UI_external_identifier',
    -        'field' => array(
    +        'field' => [
               0 => 'external_identifier',
    -        ) ,
    -        'localizable' => false,
    -        'unique' => true,
    +        ],
    +        'localizable' => FALSE,
    +        'unique' => TRUE,
             'sig' => 'civicrm_campaign::1::external_identifier',
    -      ) ,
    -    );
    +      ],
    +    ];
         return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
       }
    +
     }
    diff --git a/CRM/Campaign/DAO/CampaignGroup.php b/CRM/Campaign/DAO/CampaignGroup.php
    index 9597c097362a..fbb03b47abf5 100644
    --- a/CRM/Campaign/DAO/CampaignGroup.php
    +++ b/CRM/Campaign/DAO/CampaignGroup.php
    @@ -1,219 +1,213 @@
     __table = 'civicrm_campaign_group';
         parent::__construct();
       }
    +
       /**
        * Returns foreign keys and entity references.
        *
        * @return array
        *   [CRM_Core_Reference_Interface]
        */
    -  static function getReferenceColumns() {
    +  public static function getReferenceColumns() {
         if (!isset(Civi::$statics[__CLASS__]['links'])) {
    -      Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__);
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName() , 'entity_id', NULL, 'id', 'entity_table');
    +      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName(), 'entity_id', NULL, 'id', 'entity_table');
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
         }
         return Civi::$statics[__CLASS__]['links'];
       }
    +
       /**
        * Returns all the column names of this table
        *
        * @return array
        */
    -  static function &fields() {
    +  public static function &fields() {
         if (!isset(Civi::$statics[__CLASS__]['fields'])) {
    -      Civi::$statics[__CLASS__]['fields'] = array(
    -        'id' => array(
    +      Civi::$statics[__CLASS__]['fields'] = [
    +        'id' => [
               'name' => 'id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign Group ID') ,
    -          'description' => 'Campaign Group id.',
    -          'required' => true,
    +          'title' => ts('Campaign Group ID'),
    +          'description' => ts('Campaign Group id.'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_campaign_group.id',
               'table_name' => 'civicrm_campaign_group',
               'entity' => 'CampaignGroup',
               'bao' => 'CRM_Campaign_DAO_CampaignGroup',
               'localizable' => 0,
    -        ) ,
    -        'campaign_id' => array(
    +        ],
    +        'campaign_id' => [
               'name' => 'campaign_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Campaign') ,
    -          'description' => 'Foreign key to the activity Campaign.',
    -          'required' => true,
    +          'title' => ts('Campaign'),
    +          'description' => ts('Foreign key to the activity Campaign.'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_campaign_group.campaign_id',
               'table_name' => 'civicrm_campaign_group',
               'entity' => 'CampaignGroup',
               'bao' => 'CRM_Campaign_DAO_CampaignGroup',
               'localizable' => 0,
               'FKClassName' => 'CRM_Campaign_DAO_Campaign',
    -          'pseudoconstant' => array(
    +          'pseudoconstant' => [
                 'table' => 'civicrm_campaign',
                 'keyColumn' => 'id',
                 'labelColumn' => 'title',
    -          )
    -        ) ,
    -        'group_type' => array(
    +          ],
    +        ],
    +        'group_type' => [
               'name' => 'group_type',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Campaign Group Type') ,
    -          'description' => 'Type of Group.',
    +          'title' => ts('Campaign Group Type'),
    +          'description' => ts('Type of Group.'),
               'maxlength' => 8,
               'size' => CRM_Utils_Type::EIGHT,
    +          'where' => 'civicrm_campaign_group.group_type',
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign_group',
               'entity' => 'CampaignGroup',
               'bao' => 'CRM_Campaign_DAO_CampaignGroup',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'callback' => 'CRM_Core_SelectValues::getCampaignGroupTypes',
    -          )
    -        ) ,
    -        'entity_table' => array(
    +          ],
    +        ],
    +        'entity_table' => [
               'name' => 'entity_table',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Entity Table') ,
    -          'description' => 'Name of table where item being referenced is stored.',
    +          'title' => ts('Entity Table'),
    +          'description' => ts('Name of table where item being referenced is stored.'),
               'maxlength' => 64,
               'size' => CRM_Utils_Type::BIG,
    +          'where' => 'civicrm_campaign_group.entity_table',
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign_group',
               'entity' => 'CampaignGroup',
               'bao' => 'CRM_Campaign_DAO_CampaignGroup',
               'localizable' => 0,
    -        ) ,
    -        'entity_id' => array(
    +        ],
    +        'entity_id' => [
               'name' => 'entity_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Entity ID') ,
    -          'description' => 'Entity id of referenced table.',
    +          'title' => ts('Entity ID'),
    +          'description' => ts('Entity id of referenced table.'),
    +          'where' => 'civicrm_campaign_group.entity_id',
               'default' => 'NULL',
               'table_name' => 'civicrm_campaign_group',
               'entity' => 'CampaignGroup',
               'bao' => 'CRM_Campaign_DAO_CampaignGroup',
               'localizable' => 0,
    -        ) ,
    -      );
    +        ],
    +      ];
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
         }
         return Civi::$statics[__CLASS__]['fields'];
       }
    +
       /**
        * Return a mapping from field-name to the corresponding key (as used in fields()).
        *
        * @return array
        *   Array(string $name => string $uniqueName).
        */
    -  static function &fieldKeys() {
    +  public static function &fieldKeys() {
         if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
           Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
         }
         return Civi::$statics[__CLASS__]['fieldKeys'];
       }
    +
       /**
        * Returns the names of this table
        *
        * @return string
        */
    -  static function getTableName() {
    +  public static function getTableName() {
         return self::$_tableName;
       }
    +
       /**
        * Returns if this table needs to be logged
        *
    -   * @return boolean
    +   * @return bool
        */
    -  function getLog() {
    +  public function getLog() {
         return self::$_log;
       }
    +
       /**
        * Returns the list of fields that can be imported
        *
    @@ -221,10 +215,11 @@ function getLog() {
        *
        * @return array
        */
    -  static function &import($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'campaign_group', $prefix, array());
    +  public static function &import($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'campaign_group', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of fields that can be exported
        *
    @@ -232,15 +227,21 @@ static function &import($prefix = false) {
        *
        * @return array
        */
    -  static function &export($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'campaign_group', $prefix, array());
    +  public static function &export($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'campaign_group', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of indices
    +   *
    +   * @param bool $localize
    +   *
    +   * @return array
        */
       public static function indices($localize = TRUE) {
    -    $indices = array();
    +    $indices = [];
         return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
       }
    +
     }
    diff --git a/CRM/Campaign/DAO/Survey.php b/CRM/Campaign/DAO/Survey.php
    index f0c128bd227e..47678b9fa353 100644
    --- a/CRM/Campaign/DAO/Survey.php
    +++ b/CRM/Campaign/DAO/Survey.php
    @@ -1,493 +1,513 @@
     __table = 'civicrm_survey';
         parent::__construct();
       }
    +
       /**
        * Returns foreign keys and entity references.
        *
        * @return array
        *   [CRM_Core_Reference_Interface]
        */
    -  static function getReferenceColumns() {
    +  public static function getReferenceColumns() {
         if (!isset(Civi::$statics[__CLASS__]['links'])) {
    -      Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__);
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'created_id', 'civicrm_contact', 'id');
    -      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'last_modified_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'created_id', 'civicrm_contact', 'id');
    +      Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'last_modified_id', 'civicrm_contact', 'id');
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
         }
         return Civi::$statics[__CLASS__]['links'];
       }
    +
       /**
        * Returns all the column names of this table
        *
        * @return array
        */
    -  static function &fields() {
    +  public static function &fields() {
         if (!isset(Civi::$statics[__CLASS__]['fields'])) {
    -      Civi::$statics[__CLASS__]['fields'] = array(
    -        'id' => array(
    +      Civi::$statics[__CLASS__]['fields'] = [
    +        'id' => [
               'name' => 'id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey ID') ,
    -          'description' => 'Survey id.',
    -          'required' => true,
    +          'title' => ts('Survey ID'),
    +          'description' => ts('Survey id.'),
    +          'required' => TRUE,
    +          'where' => 'civicrm_survey.id',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'title' => array(
    +        ],
    +        'title' => [
               'name' => 'title',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Survey Title') ,
    -          'description' => 'Title of the Survey.',
    -          'required' => true,
    +          'title' => ts('Survey Title'),
    +          'description' => ts('Title of the Survey.'),
    +          'required' => TRUE,
               'maxlength' => 255,
               'size' => CRM_Utils_Type::HUGE,
    -          'import' => true,
    +          'import' => TRUE,
               'where' => 'civicrm_survey.title',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 1,
    -        ) ,
    -        'campaign_id' => array(
    +        ],
    +        'campaign_id' => [
               'name' => 'campaign_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey Campaign ID') ,
    -          'description' => 'Foreign key to the Campaign.',
    +          'title' => ts('Survey Campaign ID'),
    +          'description' => ts('Foreign key to the Campaign.'),
    +          'where' => 'civicrm_survey.campaign_id',
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
               'FKClassName' => 'CRM_Campaign_DAO_Campaign',
    -          'pseudoconstant' => array(
    +          'pseudoconstant' => [
                 'table' => 'civicrm_campaign',
                 'keyColumn' => 'id',
                 'labelColumn' => 'title',
    -          )
    -        ) ,
    -        'activity_type_id' => array(
    +          ],
    +        ],
    +        'activity_type_id' => [
               'name' => 'activity_type_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Activity Type') ,
    -          'description' => 'Implicit FK to civicrm_option_value where option_group = activity_type',
    -          'import' => true,
    +          'title' => ts('Activity Type'),
    +          'description' => ts('Implicit FK to civicrm_option_value where option_group = activity_type'),
    +          'import' => TRUE,
               'where' => 'civicrm_survey.activity_type_id',
    -          'headerPattern' => '',
    -          'dataPattern' => '',
    -          'export' => true,
    +          'export' => TRUE,
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'Select',
    -          ) ,
    -          'pseudoconstant' => array(
    +          ],
    +          'pseudoconstant' => [
                 'optionGroupName' => 'activity_type',
                 'optionEditPath' => 'civicrm/admin/options/activity_type',
    -          )
    -        ) ,
    -        'recontact_interval' => array(
    +          ],
    +        ],
    +        'recontact_interval' => [
               'name' => 'recontact_interval',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Follow up Interval') ,
    -          'description' => 'Recontact intervals for each status.',
    +          'title' => ts('Follow up Interval'),
    +          'description' => ts('Recontact intervals for each status.'),
               'rows' => 20,
               'cols' => 80,
    +          'where' => 'civicrm_survey.recontact_interval',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -          'html' => array(
    +          'html' => [
                 'type' => 'TextArea',
    -          ) ,
    -        ) ,
    -        'instructions' => array(
    +          ],
    +        ],
    +        'instructions' => [
               'name' => 'instructions',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Instructions') ,
    -          'description' => 'Script instructions for volunteers to use for the survey.',
    +          'title' => ts('Instructions'),
    +          'description' => ts('Script instructions for volunteers to use for the survey.'),
               'rows' => 20,
               'cols' => 80,
    +          'where' => 'civicrm_survey.instructions',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 1,
    -          'html' => array(
    +          'html' => [
                 'type' => 'TextArea',
    -          ) ,
    -        ) ,
    -        'release_frequency' => array(
    +          ],
    +        ],
    +        'release_frequency' => [
               'name' => 'release_frequency',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey Hold Duration') ,
    -          'description' => 'Number of days for recurrence of release.',
    +          'title' => ts('Survey Hold Duration'),
    +          'description' => ts('Number of days for recurrence of release.'),
    +          'where' => 'civicrm_survey.release_frequency',
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'max_number_of_contacts' => array(
    +        ],
    +        'max_number_of_contacts' => [
               'name' => 'max_number_of_contacts',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Maximum number of contacts') ,
    -          'description' => 'Maximum number of contacts to allow for survey.',
    +          'title' => ts('Maximum number of contacts'),
    +          'description' => ts('Maximum number of contacts to allow for survey.'),
    +          'where' => 'civicrm_survey.max_number_of_contacts',
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'default_number_of_contacts' => array(
    +        ],
    +        'default_number_of_contacts' => [
               'name' => 'default_number_of_contacts',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Default number of contacts') ,
    -          'description' => 'Default number of contacts to allow for survey.',
    +          'title' => ts('Default number of contacts'),
    +          'description' => ts('Default number of contacts to allow for survey.'),
    +          'where' => 'civicrm_survey.default_number_of_contacts',
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'is_active' => array(
    +        ],
    +        'is_active' => [
               'name' => 'is_active',
               'type' => CRM_Utils_Type::T_BOOLEAN,
    -          'title' => ts('Survey Is Active') ,
    -          'description' => 'Is this survey enabled or disabled/cancelled?',
    +          'title' => ts('Survey Is Active'),
    +          'description' => ts('Is this survey enabled or disabled/cancelled?'),
    +          'where' => 'civicrm_survey.is_active',
               'default' => '1',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'is_default' => array(
    +        ],
    +        'is_default' => [
               'name' => 'is_default',
               'type' => CRM_Utils_Type::T_BOOLEAN,
    -          'title' => ts('Is Default Survey') ,
    -          'description' => 'Is this default survey?',
    +          'title' => ts('Is Default Survey'),
    +          'description' => ts('Is this default survey?'),
    +          'where' => 'civicrm_survey.is_default',
    +          'default' => '0',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'created_id' => array(
    +        ],
    +        'created_id' => [
               'name' => 'created_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey Created By') ,
    -          'description' => 'FK to civicrm_contact, who created this Survey.',
    +          'title' => ts('Survey Created By'),
    +          'description' => ts('FK to civicrm_contact, who created this Survey.'),
    +          'where' => 'civicrm_survey.created_id',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'created_date' => array(
    +        ],
    +        'created_date' => [
               'name' => 'created_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Campaign Created Date') ,
    -          'description' => 'Date and time that Survey was created.',
    +          'title' => ts('Campaign Created Date'),
    +          'description' => ts('Date and time that Survey was created.'),
    +          'where' => 'civicrm_survey.created_date',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'last_modified_id' => array(
    +        ],
    +        'last_modified_id' => [
               'name' => 'last_modified_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey Modified') ,
    -          'description' => 'FK to civicrm_contact, who recently edited this Survey.',
    +          'title' => ts('Survey Modified'),
    +          'description' => ts('FK to civicrm_contact, who recently edited this Survey.'),
    +          'where' => 'civicrm_survey.last_modified_id',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
               'FKClassName' => 'CRM_Contact_DAO_Contact',
    -        ) ,
    -        'last_modified_date' => array(
    +        ],
    +        'last_modified_date' => [
               'name' => 'last_modified_date',
               'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
    -          'title' => ts('Survey Modified On') ,
    -          'description' => 'Date and time that Survey was edited last time.',
    +          'title' => ts('Survey Modified On'),
    +          'description' => ts('Date and time that Survey was edited last time.'),
    +          'where' => 'civicrm_survey.last_modified_date',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'result_id' => array(
    +        ],
    +        'result_id' => [
               'name' => 'result_id',
               'type' => CRM_Utils_Type::T_INT,
    -          'title' => ts('Survey Result') ,
    -          'description' => 'Used to store option group id.',
    +          'title' => ts('Survey Result'),
    +          'description' => ts('Used to store option group id.'),
    +          'where' => 'civicrm_survey.result_id',
               'default' => 'NULL',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'bypass_confirm' => array(
    +        ],
    +        'bypass_confirm' => [
               'name' => 'bypass_confirm',
               'type' => CRM_Utils_Type::T_BOOLEAN,
    -          'title' => ts('No Email Verification') ,
    -          'description' => 'Bypass the email verification.',
    +          'title' => ts('No Email Verification'),
    +          'description' => ts('Bypass the email verification.'),
    +          'where' => 'civicrm_survey.bypass_confirm',
    +          'default' => '0',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -        'thankyou_title' => array(
    +        ],
    +        'thankyou_title' => [
               'name' => 'thankyou_title',
               'type' => CRM_Utils_Type::T_STRING,
    -          'title' => ts('Thank-you Title') ,
    -          'description' => 'Title for Thank-you page (header title tag, and display at the top of the page).',
    +          'title' => ts('Thank-you Title'),
    +          'description' => ts('Title for Thank-you page (header title tag, and display at the top of the page).'),
               'maxlength' => 255,
               'size' => CRM_Utils_Type::HUGE,
    +          'where' => 'civicrm_survey.thankyou_title',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 1,
    -        ) ,
    -        'thankyou_text' => array(
    +        ],
    +        'thankyou_text' => [
               'name' => 'thankyou_text',
               'type' => CRM_Utils_Type::T_TEXT,
    -          'title' => ts('Thank-you Text') ,
    -          'description' => 'text and html allowed. displayed above result on success page',
    +          'title' => ts('Thank-you Text'),
    +          'description' => ts('text and html allowed. displayed above result on success page'),
               'rows' => 8,
               'cols' => 60,
    +          'where' => 'civicrm_survey.thankyou_text',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 1,
    -          'html' => array(
    +          'html' => [
                 'type' => 'TextArea',
    -          ) ,
    -        ) ,
    -        'is_share' => array(
    +          ],
    +        ],
    +        'is_share' => [
               'name' => 'is_share',
               'type' => CRM_Utils_Type::T_BOOLEAN,
    -          'title' => ts('Is shared through social media') ,
    -          'description' => 'Can people share the petition through social media?',
    +          'title' => ts('Is shared through social media'),
    +          'description' => ts('Can people share the petition through social media?'),
    +          'where' => 'civicrm_survey.is_share',
               'default' => '1',
               'table_name' => 'civicrm_survey',
               'entity' => 'Survey',
               'bao' => 'CRM_Campaign_BAO_Survey',
               'localizable' => 0,
    -        ) ,
    -      );
    +        ],
    +      ];
           CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
         }
         return Civi::$statics[__CLASS__]['fields'];
       }
    +
       /**
        * Return a mapping from field-name to the corresponding key (as used in fields()).
        *
        * @return array
        *   Array(string $name => string $uniqueName).
        */
    -  static function &fieldKeys() {
    +  public static function &fieldKeys() {
         if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
           Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
         }
         return Civi::$statics[__CLASS__]['fieldKeys'];
       }
    +
       /**
        * Returns the names of this table
        *
        * @return string
        */
    -  static function getTableName() {
    +  public static function getTableName() {
         return CRM_Core_DAO::getLocaleTableName(self::$_tableName);
       }
    +
       /**
        * Returns if this table needs to be logged
        *
    -   * @return boolean
    +   * @return bool
        */
    -  function getLog() {
    +  public function getLog() {
         return self::$_log;
       }
    +
       /**
        * Returns the list of fields that can be imported
        *
    @@ -495,10 +515,11 @@ function getLog() {
        *
        * @return array
        */
    -  static function &import($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'survey', $prefix, array());
    +  public static function &import($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'survey', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of fields that can be exported
        *
    @@ -506,24 +527,30 @@ static function &import($prefix = false) {
        *
        * @return array
        */
    -  static function &export($prefix = false) {
    -    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'survey', $prefix, array());
    +  public static function &export($prefix = FALSE) {
    +    $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'survey', $prefix, []);
         return $r;
       }
    +
       /**
        * Returns the list of indices
    +   *
    +   * @param bool $localize
    +   *
    +   * @return array
        */
       public static function indices($localize = TRUE) {
    -    $indices = array(
    -      'UI_activity_type_id' => array(
    +    $indices = [
    +      'UI_activity_type_id' => [
             'name' => 'UI_activity_type_id',
    -        'field' => array(
    +        'field' => [
               0 => 'activity_type_id',
    -        ) ,
    -        'localizable' => false,
    +        ],
    +        'localizable' => FALSE,
             'sig' => 'civicrm_survey::0::activity_type_id',
    -      ) ,
    -    );
    +      ],
    +    ];
         return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
       }
    +
     }
    diff --git a/CRM/Campaign/Form/Campaign.php b/CRM/Campaign/Form/Campaign.php
    index 2d4f97b55527..1723cefa9326 100644
    --- a/CRM/Campaign/Form/Campaign.php
    +++ b/CRM/Campaign/Form/Campaign.php
    @@ -1,9 +1,9 @@
     _context = CRM_Utils_Request::retrieve('context', 'String', $this);
    +    $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
     
         $this->assign('context', $this->_context);
     
         $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this);
    -    $this->_campaignId = CRM_Utils_Request::retrieve('id', 'Positive', $this);
    +    $this->_campaignId = CRM_Utils_Request::retrieve('id', 'Positive');
     
         $title = NULL;
         if ($this->_action & CRM_Core_Action::UPDATE) {
    @@ -101,11 +101,11 @@ public function preProcess() {
         //load the values;
         $this->_values = $this->get('values');
         if (!is_array($this->_values)) {
    -      $this->_values = array();
    +      $this->_values = [];
     
           // if we are editing
           if (isset($this->_campaignId) && $this->_campaignId) {
    -        $params = array('id' => $this->_campaignId);
    +        $params = ['id' => $this->_campaignId];
             CRM_Campaign_BAO_Campaign::retrieve($params, $this->_values);
           }
     
    @@ -136,18 +136,8 @@ public function preProcess() {
       public function setDefaultValues() {
         $defaults = $this->_values;
     
    -    if (isset($defaults['start_date'])) {
    -      list($defaults['start_date'], $defaults['start_date_time'])
    -        = CRM_Utils_Date::setDateDefaults($defaults['start_date'], 'activityDateTime');
    -    }
    -    else {
    -      list($defaults['start_date'], $defaults['start_date_time'])
    -        = CRM_Utils_Date::setDateDefaults();
    -    }
    -
    -    if (isset($defaults['end_date'])) {
    -      list($defaults['end_date'], $defaults['end_date_time'])
    -        = CRM_Utils_Date::setDateDefaults($defaults['end_date'], 'activityDateTime');
    +    if (empty($defaults['start_date'])) {
    +      $defaults['start_date'] = date('Y-m-d H:i:s');
         }
     
         if (!isset($defaults['is_active'])) {
    @@ -160,7 +150,7 @@ public function setDefaultValues() {
     
         $dao = new CRM_Campaign_DAO_CampaignGroup();
     
    -    $campaignGroups = array();
    +    $campaignGroups = [];
         $dao->campaign_id = $this->_campaignId;
         $dao->find();
     
    @@ -175,20 +165,20 @@ public function setDefaultValues() {
       }
     
       public function buildQuickForm() {
    +    $this->add('hidden', 'id', $this->_campaignId);
         if ($this->_action & CRM_Core_Action::DELETE) {
     
    -      $this->addButtons(array(
    -          array(
    -            'type' => 'next',
    -            'name' => ts('Delete'),
    -            'isDefault' => TRUE,
    -          ),
    -          array(
    -            'type' => 'cancel',
    -            'name' => ts('Cancel'),
    -          ),
    -        )
    -      );
    +      $this->addButtons([
    +        [
    +          'type' => 'next',
    +          'name' => ts('Delete'),
    +          'isDefault' => TRUE,
    +        ],
    +        [
    +          'type' => 'cancel',
    +          'name' => ts('Cancel'),
    +        ],
    +      ]);
           return;
         }
     
    @@ -208,13 +198,13 @@ public function buildQuickForm() {
         $this->add('textarea', 'description', ts('Description'), $attributes['description']);
     
         // add campaign start date
    -    $this->addDateTime('start_date', ts('Start Date'), TRUE, array('formatType' => 'activityDateTime'));
    +    $this->add('datepicker', 'start_date', ts('Start Date'), [], TRUE);
     
         // add campaign end date
    -    $this->addDateTime('end_date', ts('End Date'), FALSE, array('formatType' => 'activityDateTime'));
    +    $this->add('datepicker', 'end_date', ts('End Date'));
     
         // add campaign type
    -    $this->addSelect('campaign_type_id', array('onChange' => "CRM.buildCustomData( 'Campaign', this.value );"), TRUE);
    +    $this->addSelect('campaign_type_id', ['onChange' => "CRM.buildCustomData( 'Campaign', this.value );"], TRUE);
     
         // add campaign status
         $this->addSelect('status_id');
    @@ -228,8 +218,8 @@ public function buildQuickForm() {
         $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value('parent_id', $this->_values), $this->_campaignId);
         if (!empty($campaigns)) {
           $this->addElement('select', 'parent_id', ts('Parent ID'),
    -        array('' => ts('- select Parent -')) + $campaigns,
    -        array('class' => 'crm-select2')
    +        ['' => ts('- select Parent -')] + $campaigns,
    +        ['class' => 'crm-select2']
           );
         }
         $groups = CRM_Core_PseudoConstant::nestedGroup();
    @@ -238,39 +228,43 @@ public function buildQuickForm() {
           ts('Include Group(s)'),
           $groups,
           FALSE,
    -      array(
    +      [
             'multiple' => TRUE,
             'class' => 'crm-select2 huge',
             'placeholder' => ts('- none -'),
    -      )
    +      ]
         );
     
    -    $this->add('wysiwyg', 'goal_general', ts('Campaign Goals'), array('rows' => 2, 'cols' => 40));
    -    $this->add('text', 'goal_revenue', ts('Revenue Goal'), array('size' => 8, 'maxlength' => 12));
    +    $this->add('wysiwyg', 'goal_general', ts('Campaign Goals'), ['rows' => 2, 'cols' => 40]);
    +    $this->add('text', 'goal_revenue', ts('Revenue Goal'), ['size' => 8, 'maxlength' => 12]);
         $this->addRule('goal_revenue', ts('Please enter a valid money value (e.g. %1).',
    -      array(1 => CRM_Utils_Money::format('99.99', ' '))
    +      [1 => CRM_Utils_Money::format('99.99', ' ')]
         ), 'money');
     
         // is this Campaign active
         $this->addElement('checkbox', 'is_active', ts('Is Active?'));
     
    -    $this->addButtons(array(
    -        array(
    -          'type' => 'upload',
    -          'name' => ts('Save'),
    -          'isDefault' => TRUE,
    -        ),
    -        array(
    -          'type' => 'upload',
    -          'name' => ts('Save and New'),
    -          'subName' => 'new',
    -        ),
    -        array(
    -          'type' => 'cancel',
    -          'name' => ts('Cancel'),
    -        ),
    -      )
    -    );
    +    $buttons = [
    +      [
    +        'type' => 'upload',
    +        'name' => ts('Save'),
    +        'isDefault' => TRUE,
    +      ],
    +    ];
    +    // Skip this button when adding a new campaign from an entityRef
    +    if (empty($_GET['snippet']) || empty($_GET['returnExtra'])) {
    +      $buttons[] = [
    +        'type' => 'upload',
    +        'name' => ts('Save and New'),
    +        'subName' => 'new',
    +      ];
    +    }
    +    $buttons[] = [
    +      'type' => 'cancel',
    +      'name' => ts('Cancel'),
    +    ];
    +
    +    $this->addButtons($buttons);
       }
     
       /**
    @@ -285,7 +279,7 @@ public function buildQuickForm() {
        * @see valid_date
        */
       public static function formRule($fields, $files, $errors) {
    -    $errors = array();
    +    $errors = [];
     
         return empty($errors) ? TRUE : $errors;
       }
    @@ -295,31 +289,50 @@ public static function formRule($fields, $files, $errors) {
        */
       public function postProcess() {
         // store the submitted values in an array
    -    $params = $this->controller->exportValues($this->_name);
    -    $session = CRM_Core_Session::singleton();
     
    -    $groups = array();
    -    if (isset($this->_campaignId)) {
    +    $session = CRM_Core_Session::singleton();
    +    $params = $this->controller->exportValues($this->_name);
    +    // To properly save the DAO we need to ensure we don't have a blank id key passed through.
    +    if (empty($params['id'])) {
    +      unset($params['id']);
    +    }
    +    if (!empty($params['id'])) {
           if ($this->_action & CRM_Core_Action::DELETE) {
    -        CRM_Campaign_BAO_Campaign::del($this->_campaignId);
    +        CRM_Campaign_BAO_Campaign::del($params['id']);
             CRM_Core_Session::setStatus(ts('Campaign has been deleted.'), ts('Record Deleted'), 'success');
             $session->replaceUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign'));
             return;
           }
    -      $params['id'] = $this->_campaignId;
    +      $this->_campaignId = $params['id'];
         }
         else {
           $params['created_id'] = $session->get('userID');
           $params['created_date'] = date('YmdHis');
         }
         // format params
    -    $params['start_date'] = CRM_Utils_Date::processDate($params['start_date'], $params['start_date_time']);
    -    $params['end_date'] = CRM_Utils_Date::processDate($params['end_date'], $params['end_date_time'], TRUE);
         $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE);
         $params['last_modified_id'] = $session->get('userID');
         $params['last_modified_date'] = date('YmdHis');
    +    $result = self::submit($params, $this);
    +    if (!$result['is_error']) {
    +      CRM_Core_Session::setStatus(ts('Campaign %1 has been saved.', [1 => $result['values'][$result['id']]['title']]), ts('Saved'), 'success');
    +      $session->pushUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign'));
    +      $this->ajaxResponse['id'] = $result['id'];
    +      $this->ajaxResponse['label'] = $result['values'][$result['id']]['title'];
    +    }
    +    $buttonName = $this->controller->getButtonName();
    +    if ($buttonName == $this->getButtonName('upload', 'new')) {
    +      CRM_Core_Session::setStatus(ts(' You can add another Campaign.'), '', 'info');
    +      $session->replaceUserContext(CRM_Utils_System::url('civicrm/campaign/add', 'reset=1&action=add'));
    +    }
    +    else {
    +      $session->replaceUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign'));
    +    }
    +  }
     
    -    if (is_array($params['includeGroups'])) {
    +  public static function submit($params = [], $form) {
    +    $groups = [];
    +    if (!empty($params['includeGroups']) && is_array($params['includeGroups'])) {
           foreach ($params['includeGroups'] as $key => $id) {
             if ($id) {
               $groups['include'][] = $id;
    @@ -331,7 +344,7 @@ public function postProcess() {
         // delete previous includes/excludes, if campaign already existed
         $groupTableName = CRM_Contact_BAO_Group::getTableName();
         $dao = new CRM_Campaign_DAO_CampaignGroup();
    -    $dao->campaign_id = $this->_campaignId;
    +    $dao->campaign_id = $form->_campaignId;
         $dao->entity_table = $groupTableName;
         $dao->find();
         while ($dao->fetch()) {
    @@ -343,25 +356,14 @@ public function postProcess() {
           CRM_Utils_Array::value('campaign_type_id', $params)
         );
         $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
    -      $this->_campaignId,
    +      $form->_campaignId,
           'Campaign'
         );
     
    -    $result = CRM_Campaign_BAO_Campaign::create($params);
    -
    -    if ($result) {
    -      CRM_Core_Session::setStatus(ts('Campaign %1 has been saved.', array(1 => $result->title)), ts('Saved'), 'success');
    -      $session->pushUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign'));
    -    }
    -
    -    $buttonName = $this->controller->getButtonName();
    -    if ($buttonName == $this->getButtonName('upload', 'new')) {
    -      CRM_Core_Session::setStatus(ts(' You can add another Campaign.'), '', 'info');
    -      $session->replaceUserContext(CRM_Utils_System::url('civicrm/campaign/add', 'reset=1&action=add'));
    -    }
    -    else {
    -      $session->replaceUserContext(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=campaign'));
    -    }
    +    // dev/core#1067 Clean Money before passing onto BAO to do the create.
    +    $params['goal_revenue'] = CRM_Utils_Rule::cleanMoney($params['goal_revenue']);
    +    $result = civicrm_api3('Campaign', 'create', $params);
    +    return $result;
       }
     
     }
    diff --git a/CRM/Campaign/Form/Gotv.php b/CRM/Campaign/Form/Gotv.php
    index bdb78d7bcbbd..d260b93262ef 100644
    --- a/CRM/Campaign/Form/Gotv.php
    +++ b/CRM/Campaign/Form/Gotv.php
    @@ -1,9 +1,9 @@
      ts('Survey(s)'), 'url' => $url)));
    +      CRM_Utils_System::appendBreadCrumb([['title' => ts('Survey(s)'), 'url' => $url]]);
         }
     
         //set the form title.
    @@ -103,7 +103,7 @@ public function buildQuickForm() {
         CRM_Campaign_BAO_Query::buildSearchForm($this);
     
         //build the array of all search params.
    -    $this->_searchParams = array();
    +    $this->_searchParams = [];
         foreach ($this->_elements as $element) {
           $name = $element->_attributes['name'];
           if ($name == 'qfKey') {
    @@ -114,7 +114,7 @@ public function buildQuickForm() {
         $this->set('searchParams', $this->_searchParams);
         $this->assign('searchParams', json_encode($this->_searchParams));
     
    -    $defaults = array();
    +    $defaults = [];
     
         if (!$this->_surveyId) {
           $this->_surveyId = key(CRM_Campaign_BAO_Survey::getSurveys(TRUE, TRUE));
    @@ -142,7 +142,7 @@ public function buildQuickForm() {
       }
     
       public function validateIds() {
    -    $errorMessages = array();
    +    $errorMessages = [];
         //check for required permissions.
         if (!CRM_Core_Permission::check('manage campaign') &&
           !CRM_Core_Permission::check('administer CiviCampaign') &&
    @@ -153,7 +153,7 @@ public function validateIds() {
     
         $surveys = CRM_Campaign_BAO_Survey::getSurveys();
         if (empty($surveys)) {
    -      $errorMessages[] = ts("Oops. It looks like no surveys have been created. Click here to create a new survey.", array(1 => CRM_Utils_System::url('civicrm/survey/add', 'reset=1&action=add')));
    +      $errorMessages[] = ts("Oops. It looks like no surveys have been created. Click here to create a new survey.", [1 => CRM_Utils_System::url('civicrm/survey/add', 'reset=1&action=add')]);
         }
     
         if ($this->_force && !$this->_surveyId) {
    diff --git a/CRM/Campaign/Form/Petition.php b/CRM/Campaign/Form/Petition.php
    index abcfc81d394a..f203c3d88fe9 100644
    --- a/CRM/Campaign/Form/Petition.php
    +++ b/CRM/Campaign/Form/Petition.php
    @@ -1,9 +1,9 @@
     _surveyId;
    +  }
    +
       public function preProcess() {
         if (!CRM_Campaign_BAO_Campaign::accessCampaign()) {
           CRM_Utils_System::permissionDenied();
         }
     
    -    $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this);
    +    $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
     
         $this->assign('context', $this->_context);
     
    @@ -64,14 +80,8 @@ public function preProcess() {
           }
         }
     
    -    // when custom data is included in this page
    -    if (!empty($_POST['hidden_custom'])) {
    -      $this->set('type', 'Event');
    -      $this->set('entityId', $this->_surveyId);
    -      CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Survey', $this->_surveyId);
    -      CRM_Custom_Form_CustomData::buildQuickForm($this);
    -      CRM_Custom_Form_CustomData::setDefaultValues($this);
    -    }
    +    // Add custom data to form
    +    CRM_Custom_Form_CustomData::addToForm($this);
     
         $session = CRM_Core_Session::singleton();
         $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey');
    @@ -80,9 +90,9 @@ public function preProcess() {
         $this->_values = $this->get('values');
     
         if (!is_array($this->_values)) {
    -      $this->_values = array();
    +      $this->_values = [];
           if ($this->_surveyId) {
    -        $params = array('id' => $this->_surveyId);
    +        $params = ['id' => $this->_surveyId];
             CRM_Campaign_BAO_Survey::retrieve($params, $this->_values);
           }
           $this->set('values', $this->_values);
    @@ -90,8 +100,6 @@ public function preProcess() {
     
         $this->assign('action', $this->_action);
         $this->assign('surveyId', $this->_surveyId);
    -    // for custom data
    -    $this->assign('entityID', $this->_surveyId);
     
         if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::DELETE)) {
           $this->_surveyId = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE);
    @@ -108,7 +116,7 @@ public function preProcess() {
         $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=petition');
         $session->pushUserContext($url);
     
    -    CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('Petition Dashboard'), 'url' => $url)));
    +    CRM_Utils_System::appendBreadCrumb([['title' => ts('Petition Dashboard'), 'url' => $url]]);
       }
     
       /**
    @@ -121,20 +129,20 @@ public function preProcess() {
       public function setDefaultValues() {
         $defaults = $this->_values;
     
    -    $ufContactJoinParams = array(
    +    $ufContactJoinParams = [
           'entity_table' => 'civicrm_survey',
           'entity_id' => $this->_surveyId,
           'weight' => 2,
    -    );
    +    ];
     
         if ($ufContactGroupId = CRM_Core_BAO_UFJoin::findUFGroupId($ufContactJoinParams)) {
           $defaults['contact_profile_id'] = $ufContactGroupId;
         }
    -    $ufActivityJoinParams = array(
    +    $ufActivityJoinParams = [
           'entity_table' => 'civicrm_survey',
           'entity_id' => $this->_surveyId,
           'weight' => 1,
    -    );
    +    ];
     
         if ($ufActivityGroupId = CRM_Core_BAO_UFJoin::findUFGroupId($ufActivityJoinParams)) {
           $defaults['profile_id'] = $ufActivityGroupId;
    @@ -152,22 +160,21 @@ public function setDefaultValues() {
         return $defaults;
       }
     
    -
       public function buildQuickForm() {
     
         if ($this->_action & CRM_Core_Action::DELETE) {
           $this->addButtons(
    -        array(
    -          array(
    +        [
    +          [
                 'type' => 'next',
                 'name' => ts('Delete'),
                 'isDefault' => TRUE,
    -          ),
    -          array(
    +          ],
    +          [
                 'type' => 'cancel',
                 'name' => ts('Cancel'),
    -          ),
    -        )
    +          ],
    +        ]
           );
           return;
         }
    @@ -182,24 +189,26 @@ public function buildQuickForm() {
         // script / instructions / description of petition purpose
         $this->add('wysiwyg', 'instructions', ts('Introduction'), $attributes['instructions']);
     
    -    // Campaign id
    -    $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value('campaign_id', $this->_values));
    -    $this->add('select', 'campaign_id', ts('Campaign'), array('' => ts('- select -')) + $campaigns);
    +    $this->addEntityRef('campaign_id', ts('Campaign'), [
    +      'entity' => 'Campaign',
    +      'create' => TRUE,
    +      'select' => ['minimumInputLength' => 0],
    +    ]);
     
    -    $customContactProfiles = CRM_Core_BAO_UFGroup::getProfiles(array('Individual'));
    +    $customContactProfiles = CRM_Core_BAO_UFGroup::getProfiles(['Individual']);
         // custom group id
         $this->add('select', 'contact_profile_id', ts('Contact Profile'),
    -      array(
    +      [
             '' => ts('- select -'),
    -      ) + $customContactProfiles, TRUE
    +      ] + $customContactProfiles, TRUE
         );
     
    -    $customProfiles = CRM_Core_BAO_UFGroup::getProfiles(array('Activity'));
    +    $customProfiles = CRM_Core_BAO_UFGroup::getProfiles(['Activity']);
         // custom group id
         $this->add('select', 'profile_id', ts('Activity Profile'),
    -      array(
    +      [
             '' => ts('- select -'),
    -      ) + $customProfiles
    +      ] + $customProfiles
         );
     
         // thank you title and text (html allowed in text)
    @@ -220,26 +229,26 @@ public function buildQuickForm() {
     
         // add buttons
         $this->addButtons(
    -      array(
    -        array(
    +      [
    +        [
               'type' => 'next',
               'name' => ts('Save'),
               'isDefault' => TRUE,
    -        ),
    -        array(
    +        ],
    +        [
               'type' => 'next',
               'name' => ts('Save and New'),
               'subName' => 'new',
    -        ),
    -        array(
    +        ],
    +        [
               'type' => 'cancel',
               'name' => ts('Cancel'),
    -        ),
    -      )
    +        ],
    +      ]
         );
     
         // add a form rule to check default value
    -    $this->addFormRule(array('CRM_Campaign_Form_Petition', 'formRule'), $this);
    +    $this->addFormRule(['CRM_Campaign_Form_Petition', 'formRule'], $this);
       }
     
       /**
    @@ -250,14 +259,14 @@ public function buildQuickForm() {
        * @return array|bool
        */
       public static function formRule($fields, $files, $form) {
    -    $errors = array();
    +    $errors = [];
         // Petitions should be unique by: title, campaign ID (if assigned) and activity type ID
         // NOTE: This class is called for both Petition create / update AND for Survey Results tab, but this rule is only for Petition.
    -    $where = array('activity_type_id = %1', 'title = %2');
    -    $params = array(
    -      1 => array($fields['activity_type_id'], 'Integer'),
    -      2 => array($fields['title'], 'String'),
    -    );
    +    $where = ['activity_type_id = %1', 'title = %2'];
    +    $params = [
    +      1 => [$fields['activity_type_id'], 'Integer'],
    +      2 => [$fields['title'], 'String'],
    +    ];
         $uniqueRuleErrorMessage = ts('This title is already associated with the selected activity type. Please specify a unique title.');
     
         if (empty($fields['campaign_id'])) {
    @@ -265,14 +274,14 @@ public static function formRule($fields, $files, $form) {
         }
         else {
           $where[] = 'campaign_id = %3';
    -      $params[3] = array($fields['campaign_id'], 'Integer');
    +      $params[3] = [$fields['campaign_id'], 'Integer'];
           $uniqueRuleErrorMessage = ts('This title is already associated with the selected campaign and activity type. Please specify a unique title.');
         }
     
         // Exclude current Petition row if UPDATE.
         if ($form->_surveyId) {
           $where[] = 'id != %4';
    -      $params[4] = array($form->_surveyId, 'Integer');
    +      $params[4] = [$form->_surveyId, 'Integer'];
         }
     
         $whereClause = implode(' AND ', $where);
    @@ -290,7 +299,6 @@ public static function formRule($fields, $files, $form) {
         return empty($errors) ? TRUE : $errors;
       }
     
    -
       public function postProcess() {
         // store the submitted values in an array
         $params = $this->controller->exportValues($this->_name);
    @@ -321,21 +329,17 @@ public function postProcess() {
         $params['is_active'] = CRM_Utils_Array::value('is_active', $params, 0);
         $params['is_default'] = CRM_Utils_Array::value('is_default', $params, 0);
     
    -    $customFields = CRM_Core_BAO_CustomField::getFields('Survey');
    -    $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
    -      $this->_surveyId,
    -      'Survey'
    -    );
    +    $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->getEntityId(), $this->getDefaultEntity());
     
         $surveyId = CRM_Campaign_BAO_Survey::create($params);
     
         // also update the ProfileModule tables
    -    $ufJoinParams = array(
    +    $ufJoinParams = [
           'is_active' => 1,
           'module' => 'CiviCampaign',
           'entity_table' => 'civicrm_survey',
           'entity_id' => $surveyId->id,
    -    );
    +    ];
     
         // first delete all past entries
         if ($this->_surveyId) {
    diff --git a/CRM/Campaign/Form/Petition/Signature.php b/CRM/Campaign/Form/Petition/Signature.php
    index f144dc4076b0..29e398ef1850 100644
    --- a/CRM/Campaign/Form/Petition/Signature.php
    +++ b/CRM/Campaign/Form/Petition/Signature.php
    @@ -1,9 +1,9 @@
     _surveyId;
    -    $this->petition = array();
    +    $this->petition = [];
         CRM_Campaign_BAO_Survey::retrieve($params, $this->petition);
         if (empty($this->petition)) {
           CRM_Core_Error::fatal('Petition doesn\'t exist.');
    @@ -201,12 +201,12 @@ public function preProcess() {
     
         // add the custom contact and activity profile fields to the signature form
     
    -    $ufJoinParams = array(
    +    $ufJoinParams = [
           'entity_id' => $this->_surveyId,
           'entity_table' => 'civicrm_survey',
           'module' => 'CiviCampaign',
           'weight' => 2,
    -    );
    +    ];
     
         $this->_contactProfileId = CRM_Core_BAO_UFJoin::findUFGroupId($ufJoinParams);
         if ($this->_contactProfileId) {
    @@ -231,7 +231,7 @@ public function preProcess() {
        * Set default values for the form.
        */
       public function setDefaultValues() {
    -    $this->_defaults = array();
    +    $this->_defaults = [];
         if ($this->_contactId) {
           CRM_Core_BAO_UFGroup::setProfileDefaults($this->_contactId, $this->_contactProfileFields, $this->_defaults, TRUE);
           if ($this->_activityProfileId) {
    @@ -296,14 +296,13 @@ public function buildQuickForm() {
           $this->buildCustom($this->_activityProfileId, 'petitionActivityProfile');
         }
         // add buttons
    -    $this->addButtons(array(
    -        array(
    -          'type' => 'upload',
    -          'name' => ts('Sign the Petition'),
    -          'isDefault' => TRUE,
    -        ),
    -      )
    -    );
    +    $this->addButtons([
    +      [
    +        'type' => 'upload',
    +        'name' => ts('Sign the Petition'),
    +        'isDefault' => TRUE,
    +      ],
    +    ]);
       }
     
       /**
    @@ -318,7 +317,7 @@ public function buildQuickForm() {
        * @return array|bool
        */
       public static function formRule($fields, $files, $errors) {
    -    $errors = array();
    +    $errors = [];
     
         return empty($errors) ? TRUE : $errors;
       }
    @@ -371,11 +370,11 @@ public function postProcess() {
           $ids[0] = $this->_contactId;
         }
         else {
    -      $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $this->_ctype, 'Unsupervised', array(), FALSE);
    +      $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, $this->_ctype, 'Unsupervised', [], FALSE);
         }
     
         $petition_params['id'] = $this->_surveyId;
    -    $petition = array();
    +    $petition = [];
         CRM_Campaign_BAO_Survey::retrieve($petition_params, $petition);
     
         switch (count($ids)) {
    @@ -606,9 +605,7 @@ public function buildCustom($id, $name, $viewOnly = FALSE) {
             }
     
             if ($addCaptcha && !$viewOnly) {
    -          $captcha = CRM_Utils_ReCAPTCHA::singleton();
    -          $captcha->add($this);
    -          $this->assign("isCaptcha", TRUE);
    +          CRM_Utils_ReCAPTCHA::enableCaptchaOnForm($this);
             }
           }
         }
    diff --git a/CRM/Campaign/Form/Search.php b/CRM/Campaign/Form/Search.php
    index da3d9fafbcb1..7d380334549a 100644
    --- a/CRM/Campaign/Form/Search.php
    +++ b/CRM/Campaign/Form/Search.php
    @@ -1,9 +1,9 @@
     _printButtonName = $this->getButtonName('next', 'print');
         $this->_actionButtonName = $this->getButtonName('next', 'action');
     
    -    //we allow the controller to set force/reset externally,
    -    //useful when we are being driven by the wizard framework
    -    $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this);
    -    $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE);
    -    $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'search');
    -    $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean');
    +    $this->loadStandardSearchOptionsFromUrl();
     
         //operation for state machine.
         $this->_operation = CRM_Utils_Request::retrieve('op', 'String', $this, FALSE, 'reserve');
    @@ -205,14 +201,13 @@ public function buildQuickForm() {
             $this->addRowSelectors($rows);
           }
     
    -      $permission = CRM_Core_Permission::getPermission();
    -      $allTasks = CRM_Campaign_Task::permissionedTaskTitles($permission);
    +      $allTasks = CRM_Campaign_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission());
     
           //hack to serve right page to state machine.
           $taskMapping = array(
    -        'interview' => 1,
    -        'reserve' => 2,
    -        'release' => 3,
    +        'interview' => CRM_Campaign_Task::INTERVIEW,
    +        'reserve' => CRM_Campaign_Task::RESERVE,
    +        'release' => CRM_Campaign_Task::RELEASE,
           );
     
           $currentTaskValue = CRM_Utils_Array::value($this->_operation, $taskMapping);
    @@ -345,11 +340,7 @@ public function formatParams() {
     
         //apply filter of survey contact type for search.
         $contactType = CRM_Campaign_BAO_Survey::getSurveyContactType(CRM_Utils_Array::value('campaign_survey_id', $this->_formValues));
    -    if ($contactType && in_array($this->_operation, array(
    -        'reserve',
    -        'interview',
    -      ))
    -    ) {
    +    if ($contactType && in_array($this->_operation, ['reserve', 'interview'])) {
           $this->_formValues['contact_type'][$contactType] = 1;
         }
     
    diff --git a/CRM/Campaign/Form/Search/Campaign.php b/CRM/Campaign/Form/Search/Campaign.php
    index f7a837716449..f292272399c5 100644
    --- a/CRM/Campaign/Form/Search/Campaign.php
    +++ b/CRM/Campaign/Form/Search/Campaign.php
    @@ -1,9 +1,9 @@
     add('text', 'description', ts('Description'), $attributes['description']);
     
    -    //campaign start date.
    -    $this->addDate('start_date', ts('From'), FALSE, array('formatType' => 'searchDate'));
    -
    -    //campaign end date.
    -    $this->addDate('end_date', ts('To'), FALSE, array('formatType' => 'searchDate'));
    +    $this->add('datepicker', 'start_date', ts('Campaign Start Date'), [], FALSE, ['time' => FALSE]);
    +    $this->add('datepicker', 'end_date', ts('Campaign End Date'), [], FALSE, ['time' => FALSE]);
     
         //campaign type.
         $campaignTypes = CRM_Campaign_PseudoConstant::campaignType();
         $this->add('select', 'campaign_type_id', ts('Campaign Type'),
    -      array(
    +      [
             '' => ts('- select -'),
    -      ) + $campaignTypes
    +      ] + $campaignTypes
         );
     
         $this->set('campaignTypes', $campaignTypes);
    @@ -98,23 +104,22 @@ public function buildQuickForm() {
         //campaign status
         $campaignStatus = CRM_Campaign_PseudoConstant::campaignStatus();
         $this->addElement('select', 'status_id', ts('Campaign Status'),
    -      array(
    +      [
             '' => ts('- select -'),
    -      ) + $campaignStatus
    +      ] + $campaignStatus
         );
         $this->set('campaignStatus', $campaignStatus);
         $this->assign('campaignStatus', json_encode($campaignStatus));
     
         //active campaigns
    -    $this->addElement('select', 'is_active', ts('Is Active?'), array(
    +    $this->addElement('select', 'is_active', ts('Is Active?'), [
           '' => ts('- select -'),
           '0' => ts('Yes'),
           '1' => ts('No'),
    -        )
    -    );
    +    ]);
     
         //build the array of all search params.
    -    $this->_searchParams = array();
    +    $this->_searchParams = [];
         foreach ($this->_elements as $element) {
           $name = $element->_attributes['name'];
           $label = $element->_label;
    diff --git a/CRM/Campaign/Form/Search/Petition.php b/CRM/Campaign/Form/Search/Petition.php
    index d9bbabe0f9ee..d23e9e88295c 100644
    --- a/CRM/Campaign/Form/Search/Petition.php
    +++ b/CRM/Campaign/Form/Search/Petition.php
    @@ -1,9 +1,9 @@
     add('select', 'petition_campaign_id', ts('Campaign'), array('' => ts('- select -')) + $campaigns);
    +    $this->add('select', 'petition_campaign_id', ts('Campaign'), ['' => ts('- select -')] + $campaigns);
         $this->set('petitionCampaigns', $campaigns);
         $this->assign('petitionCampaigns', json_encode($campaigns));
     
         //build the array of all search params.
    -    $this->_searchParams = array();
    +    $this->_searchParams = [];
         foreach ($this->_elements as $element) {
           $name = $element->_attributes['name'];
           $label = $element->_label;
    diff --git a/CRM/Campaign/Form/Search/Survey.php b/CRM/Campaign/Form/Search/Survey.php
    index 781f61101b7a..3cf35bf2ee5f 100644
    --- a/CRM/Campaign/Form/Search/Survey.php
    +++ b/CRM/Campaign/Form/Search/Survey.php
    @@ -1,9 +1,9 @@
     add('select', 'activity_type_id',
    -      ts('Activity Type'), array(
    +      ts('Activity Type'), [
             '' => ts('- select -'),
    -      ) + $surveyTypes
    +      ] + $surveyTypes
         );
         $this->set('surveyTypes', $surveyTypes);
         $this->assign('surveyTypes', json_encode($surveyTypes));
     
         //campaigns
         $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, FALSE, FALSE, FALSE, TRUE);
    -    $this->add('select', 'survey_campaign_id', ts('Campaign'), array('' => ts('- select -')) + $campaigns);
    +    $this->add('select', 'survey_campaign_id', ts('Campaign'), ['' => ts('- select -')] + $campaigns);
         $this->set('surveyCampaigns', $campaigns);
         $this->assign('surveyCampaigns', json_encode($campaigns));
     
         //build the array of all search params.
    -    $this->_searchParams = array();
    +    $this->_searchParams = [];
         foreach ($this->_elements as $element) {
           $name = $element->_attributes['name'];
           $label = $element->_label;
    diff --git a/CRM/Campaign/Form/Survey.php b/CRM/Campaign/Form/Survey.php
    index ea1113db7d2a..05eec10ceee6 100644
    --- a/CRM/Campaign/Form/Survey.php
    +++ b/CRM/Campaign/Form/Survey.php
    @@ -1,9 +1,9 @@
     _surveyId;
    +  }
    +
       public function preProcess() {
         if (!CRM_Campaign_BAO_Campaign::accessCampaign()) {
           CRM_Utils_System::permissionDenied();
    @@ -68,29 +84,23 @@ public function preProcess() {
         if ($this->_surveyId) {
           $this->_single = TRUE;
     
    -      $params = array('id' => $this->_surveyId);
    +      $params = ['id' => $this->_surveyId];
           CRM_Campaign_BAO_Survey::retrieve($params, $surveyInfo);
           $this->_surveyTitle = $surveyInfo['title'];
           $this->assign('surveyTitle', $this->_surveyTitle);
    -      CRM_Utils_System::setTitle(ts('Configure Survey - %1', array(1 => $this->_surveyTitle)));
    +      CRM_Utils_System::setTitle(ts('Configure Survey - %1', [1 => $this->_surveyTitle]));
         }
     
         $this->assign('action', $this->_action);
         $this->assign('surveyId', $this->_surveyId);
     
    -    // when custom data is included in this page
    -    if (!empty($_POST['hidden_custom'])) {
    -      $this->set('type', 'Survey');
    -      $this->set('entityId', $this->_surveyId);
    -      CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'Survey', $this->_surveyId);
    -      CRM_Custom_Form_CustomData::buildQuickForm($this);
    -      CRM_Custom_Form_CustomData::setDefaultValues($this);
    -    }
    +    // Add custom data to form
    +    CRM_Custom_Form_CustomData::addToForm($this);
     
         // CRM-11480, CRM-11682
         // Preload libraries required by the "Questions" tab
         CRM_UF_Page_ProfileEditor::registerProfileScripts();
    -    CRM_UF_Page_ProfileEditor::registerSchemas(array('IndividualModel', 'ActivityModel'));
    +    CRM_UF_Page_ProfileEditor::registerSchemas(['IndividualModel', 'ActivityModel']);
     
         CRM_Campaign_Form_Survey_TabHeader::build($this);
       }
    @@ -101,39 +111,39 @@ public function preProcess() {
       public function buildQuickForm() {
         $session = CRM_Core_Session::singleton();
         if ($this->_surveyId) {
    -      $buttons = array(
    -        array(
    +      $buttons = [
    +        [
               'type' => 'upload',
               'name' => ts('Save'),
               'isDefault' => TRUE,
    -        ),
    -        array(
    +        ],
    +        [
               'type' => 'upload',
               'name' => ts('Save and Done'),
               'subName' => 'done',
    -        ),
    -        array(
    +        ],
    +        [
               'type' => 'upload',
               'name' => ts('Save and Next'),
               'spacing' => '                 ',
               'subName' => 'next',
    -        ),
    -      );
    +        ],
    +      ];
         }
         else {
    -      $buttons = array(
    -        array(
    +      $buttons = [
    +        [
               'type' => 'upload',
               'name' => ts('Continue'),
               'spacing' => '                 ',
               'isDefault' => TRUE,
    -        ),
    -      );
    +        ],
    +      ];
         }
    -    $buttons[] = array(
    +    $buttons[] = [
           'type' => 'cancel',
           'name' => ts('Cancel'),
    -    );
    +    ];
         $this->addButtons($buttons);
     
         $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey');
    @@ -148,7 +158,7 @@ public function endPostProcess() {
             $tabTitle = 'Main settings';
           }
           $subPage = strtolower($className);
    -      CRM_Core_Session::setStatus(ts("'%1' have been saved.", array(1 => $tabTitle)), ts('Saved'), 'success');
    +      CRM_Core_Session::setStatus(ts("'%1' have been saved.", [1 => $tabTitle]), ts('Saved'), 'success');
     
           $this->postProcessHook();
     
    diff --git a/CRM/Campaign/Form/Survey/Delete.php b/CRM/Campaign/Form/Survey/Delete.php
    index 503b4b1160a7..0567a598e4f5 100644
    --- a/CRM/Campaign/Form/Survey/Delete.php
    +++ b/CRM/Campaign/Form/Survey/Delete.php
    @@ -1,9 +1,9 @@
     _surveyId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
    -    $params = array('id' => $this->_surveyId);
    +    $params = ['id' => $this->_surveyId];
         CRM_Campaign_BAO_Survey::retrieve($params, $surveyInfo);
         $this->_surveyTitle = $surveyInfo['title'];
         $this->assign('surveyTitle', $this->_surveyTitle);
    @@ -71,18 +70,17 @@ public function preProcess() {
        * Build the form object.
        */
       public function buildQuickForm() {
    -    $this->addButtons(array(
    -        array(
    -          'type' => 'next',
    -          'name' => ts('Delete'),
    -          'isDefault' => TRUE,
    -        ),
    -        array(
    -          'type' => 'cancel',
    -          'name' => ts('Cancel'),
    -        ),
    -      )
    -    );
    +    $this->addButtons([
    +      [
    +        'type' => 'next',
    +        'name' => ts('Delete'),
    +        'isDefault' => TRUE,
    +      ],
    +      [
    +        'type' => 'cancel',
    +        'name' => ts('Cancel'),
    +      ],
    +    ]);
       }
     
       /**
    @@ -91,7 +89,7 @@ public function buildQuickForm() {
       public function postProcess() {
         if ($this->_surveyId) {
           CRM_Campaign_BAO_Survey::del($this->_surveyId);
    -      CRM_Core_Session::setStatus('', ts("'%1' survey has been deleted.", array(1 => $this->_surveyTitle)), 'success');
    +      CRM_Core_Session::setStatus('', ts("'%1' survey has been deleted.", [1 => $this->_surveyTitle]), 'success');
           CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'));
         }
         else {
    diff --git a/CRM/Campaign/Form/Survey/Main.php b/CRM/Campaign/Form/Survey/Main.php
    index aae8c13410c8..68f074ec19d2 100644
    --- a/CRM/Campaign/Form/Survey/Main.php
    +++ b/CRM/Campaign/Form/Survey/Main.php
    @@ -1,9 +1,9 @@
     _context = CRM_Utils_Request::retrieve('context', 'String', $this);
    +    $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
     
         $this->assign('context', $this->_context);
     
    @@ -70,22 +66,19 @@ public function preProcess() {
           CRM_Utils_System::setTitle(ts('Configure Survey') . ' - ' . $this->_surveyTitle);
         }
     
    -    // when custom data is included in this page
    -    if (!empty($_POST['hidden_custom'])) {
    -      CRM_Custom_Form_CustomData::preProcess($this);
    -      CRM_Custom_Form_CustomData::buildQuickForm($this);
    -    }
    +    // Add custom data to form
    +    CRM_Custom_Form_CustomData::addToForm($this);
     
         if ($this->_name != 'Petition') {
           $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey');
    -      CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('Survey Dashboard'), 'url' => $url)));
    +      CRM_Utils_System::appendBreadCrumb([['title' => ts('Survey Dashboard'), 'url' => $url]]);
         }
     
         $this->_values = $this->get('values');
         if (!is_array($this->_values)) {
    -      $this->_values = array();
    +      $this->_values = [];
           if ($this->_surveyId) {
    -        $params = array('id' => $this->_surveyId);
    +        $params = ['id' => $this->_surveyId];
             CRM_Campaign_BAO_Survey::retrieve($params, $this->_values);
           }
           $this->set('values', $this->_values);
    @@ -93,8 +86,6 @@ public function preProcess() {
     
         $this->assign('action', $this->_action);
         $this->assign('surveyId', $this->_surveyId);
    -    // for custom data
    -    $this->assign('entityID', $this->_surveyId);
       }
     
       /**
    @@ -136,31 +127,31 @@ public function setDefaultValues() {
        * Build the form object.
        */
       public function buildQuickForm() {
    -
         $this->add('text', 'title', ts('Title'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'title'), TRUE);
     
    -    $surveyActivityTypes = CRM_Campaign_BAO_Survey::getSurveyActivityType();
         // Activity Type id
    -    $this->addSelect('activity_type_id', array('option_url' => 'civicrm/admin/campaign/surveyType'), TRUE);
    +    $this->addSelect('activity_type_id', ['option_url' => 'civicrm/admin/campaign/surveyType'], TRUE);
     
    -    // Campaign id
    -    $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value('campaign_id', $this->_values));
    -    $this->add('select', 'campaign_id', ts('Campaign'), array('' => ts('- select -')) + $campaigns);
    +    $this->addEntityRef('campaign_id', ts('Campaign'), [
    +      'entity' => 'Campaign',
    +      'create' => TRUE,
    +      'select' => ['minimumInputLength' => 0],
    +    ]);
     
         // script / instructions
    -    $this->add('wysiwyg', 'instructions', ts('Instructions for interviewers'), array('rows' => 5, 'cols' => 40));
    +    $this->add('wysiwyg', 'instructions', ts('Instructions for interviewers'), ['rows' => 5, 'cols' => 40]);
     
         // release frequency
    -    $this->add('text', 'release_frequency', ts('Release Frequency'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'release_frequency'));
    +    $this->add('number', 'release_frequency', ts('Release Frequency'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'release_frequency'));
     
         $this->addRule('release_frequency', ts('Release Frequency interval should be a positive number.'), 'positiveInteger');
     
         // max reserved contacts at a time
    -    $this->add('text', 'default_number_of_contacts', ts('Maximum reserved at one time'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'default_number_of_contacts'));
    +    $this->add('number', 'default_number_of_contacts', ts('Maximum reserved at one time'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'default_number_of_contacts'));
         $this->addRule('default_number_of_contacts', ts('Maximum reserved at one time should be a positive number'), 'positiveInteger');
     
         // total reserved per interviewer
    -    $this->add('text', 'max_number_of_contacts', ts('Total reserved per interviewer'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'max_number_of_contacts'));
    +    $this->add('number', 'max_number_of_contacts', ts('Total reserved per interviewer'), CRM_Core_DAO::getAttribute('CRM_Campaign_DAO_Survey', 'max_number_of_contacts'));
         $this->addRule('max_number_of_contacts', ts('Total reserved contacts should be a positive number'), 'positiveInteger');
     
         // is active ?
    @@ -195,22 +186,20 @@ public function postProcess() {
         $params['is_active'] = CRM_Utils_Array::value('is_active', $params, 0);
         $params['is_default'] = CRM_Utils_Array::value('is_default', $params, 0);
     
    -    $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
    -      $this->_surveyId,
    -      'Survey'
    -    );
    +    $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->getEntityId(), $this->getDefaultEntity());
    +
         $survey = CRM_Campaign_BAO_Survey::create($params);
         $this->_surveyId = $survey->id;
     
         if (!empty($this->_values['result_id'])) {
           $query = "SELECT COUNT(*) FROM civicrm_survey WHERE result_id = %1";
           $countSurvey = (int) CRM_Core_DAO::singleValueQuery($query,
    -        array(
    -          1 => array(
    +        [
    +          1 => [
                 $this->_values['result_id'],
                 'Positive',
    -          ),
    -        )
    +          ],
    +        ]
           );
           // delete option group if no any survey is using it.
           if (!$countSurvey) {
    diff --git a/CRM/Campaign/Form/Survey/Questions.php b/CRM/Campaign/Form/Survey/Questions.php
    index 1dfd387d7fb2..4a20450668a4 100644
    --- a/CRM/Campaign/Form/Survey/Questions.php
    +++ b/CRM/Campaign/Form/Survey/Questions.php
    @@ -1,9 +1,9 @@
      'civicrm_survey',
           'module' => 'CiviCampaign',
           'entity_id' => $this->_surveyId,
    -    );
    +    ];
     
         list($defaults['contact_profile_id'], $second)
           = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams);
    @@ -66,28 +66,29 @@ public function setDefaultValues() {
       public function buildQuickForm() {
         $subTypeId = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $this->_surveyId, 'activity_type_id');
         if (!CRM_Core_BAO_CustomGroup::autoCreateByActivityType($subTypeId)) {
    -      $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); // everything
    +      // everything
    +      $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE);
           // FIXME: Displays weird "/\ Array" message; doesn't work with tabs
           CRM_Core_Session::setStatus(
             ts(
               'There are no custom data sets for activity type "%1". To create one, click here.',
    -          array(
    +          [
                 1 => $activityTypes[$subTypeId],
                 2 => CRM_Utils_System::url('civicrm/admin/custom/group', 'action=add&reset=1'),
                 3 => '_blank',
    -          )
    +          ]
             )
           );
         }
     
         $allowCoreTypes = CRM_Campaign_BAO_Survey::surveyProfileTypes();
    -    $allowSubTypes = array(
    -      'ActivityType' => array($subTypeId),
    -    );
    -    $entities = array(
    -      array('entity_name' => 'contact_1', 'entity_type' => 'IndividualModel'),
    -      array('entity_name' => 'activity_1', 'entity_type' => 'ActivityModel', 'entity_sub_type' => $subTypeId),
    -    );
    +    $allowSubTypes = [
    +      'ActivityType' => [$subTypeId],
    +    ];
    +    $entities = [
    +      ['entity_name' => 'contact_1', 'entity_type' => 'IndividualModel'],
    +      ['entity_name' => 'activity_1', 'entity_type' => 'ActivityModel', 'entity_sub_type' => $subTypeId],
    +    ];
         $this->addProfileSelector('contact_profile_id', ts('Contact Info'), $allowCoreTypes, $allowSubTypes, $entities);
         $this->addProfileSelector('activity_profile_id', ts('Questions'), $allowCoreTypes, $allowSubTypes, $entities);
         // Note: Because this is in a tab, we also preload the schema via CRM_Campaign_Form_Survey::preProcess
    @@ -95,7 +96,6 @@ public function buildQuickForm() {
         parent::buildQuickForm();
       }
     
    -
       /**
        * Process the form.
        */
    @@ -104,17 +104,17 @@ public function postProcess() {
         $params = $this->controller->exportValues($this->_name);
     
         // also update the ProfileModule tables
    -    $ufJoinParams = array(
    +    $ufJoinParams = [
           'is_active' => 1,
           'module' => 'CiviCampaign',
           'entity_table' => 'civicrm_survey',
           'entity_id' => $this->_surveyId,
    -    );
    +    ];
     
         // first delete all past entries
         CRM_Core_BAO_UFJoin::deleteAll($ufJoinParams);
     
    -    $uf = array();
    +    $uf = [];
         $wt = 2;
         if (!empty($params['contact_profile_id'])) {
           $uf[1] = $params['contact_profile_id'];
    diff --git a/CRM/Campaign/Form/Survey/Results.php b/CRM/Campaign/Form/Survey/Results.php
    index 15c5dbfc98ca..56560418d1f3 100644
    --- a/CRM/Campaign/Form/Survey/Results.php
    +++ b/CRM/Campaign/Form/Survey/Results.php
    @@ -1,9 +1,9 @@
     _values = $this->get('values');
         if (!is_array($this->_values)) {
    -      $this->_values = array();
    +      $this->_values = [];
           if ($this->_surveyId) {
    -        $params = array('id' => $this->_surveyId);
    +        $params = ['id' => $this->_surveyId];
             CRM_Campaign_BAO_Survey::retrieve($params, $this->_values);
           }
           $this->set('values', $this->_values);
         }
     
         $query = "SELECT MAX(id) as id, title FROM civicrm_report_instance WHERE name = %1 GROUP BY id";
    -    $params = array(1 => array("survey_{$this->_surveyId}", 'String'));
    +    $params = [1 => ["survey_{$this->_surveyId}", 'String']];
         $result = CRM_Core_DAO::executeQuery($query, $params);
         if ($result->fetch()) {
           $this->_reportId = $result->id;
    @@ -100,43 +103,43 @@ public function buildQuickForm() {
         $optionGroups = CRM_Campaign_BAO_Survey::getResultSets();
     
         if (empty($optionGroups)) {
    -      $optionTypes = array('1' => ts('Create new result set'));
    +      $optionTypes = ['1' => ts('Create new result set')];
         }
         else {
    -      $optionTypes = array(
    +      $optionTypes = [
             '1' => ts('Create new result set'),
             '2' => ts('Use existing result set'),
    -      );
    +      ];
           $this->add('select',
             'option_group_id',
             ts('Select Result Set'),
    -        array(
    +        [
               '' => ts('- select -'),
    -        ) + $optionGroups, FALSE,
    -        array('onChange' => 'loadOptionGroup( )')
    +        ] + $optionGroups, FALSE,
    +        ['onChange' => 'loadOptionGroup( )']
           );
         }
     
         $element = &$this->addRadio('option_type',
           ts('Survey Responses'),
           $optionTypes,
    -      array(
    +      [
             'onclick' => "showOptionSelect();",
    -      ), '
    ', TRUE + ], '
    ', TRUE ); if (empty($optionGroups) || empty($this->_values['result_id'])) { - $this->setdefaults(array('option_type' => 1)); + $this->setdefaults(['option_type' => 1]); } elseif (!empty($this->_values['result_id'])) { - $this->setdefaults(array( + $this->setdefaults([ 'option_type' => 2, 'option_group_id' => $this->_values['result_id'], - )); + ]); } // form fields of Custom Option rows - $defaultOption = array(); + $defaultOption = []; $_showHide = new CRM_Core_ShowHideBlocks('', ''); $optionAttributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue'); @@ -165,7 +168,7 @@ public function buildQuickForm() { ); // weight - $this->add('text', "option_weight[$i]", ts('Order'), + $this->add('number', "option_weight[$i]", ts('Order'), $optionAttributes['weight'] ); @@ -190,10 +193,10 @@ public function buildQuickForm() { $this->freeze('report_title'); } - $this->addFormRule(array( + $this->addFormRule([ 'CRM_Campaign_Form_Survey_Results', 'formRule', - ), $this); + ], $this); parent::buildQuickForm(); } @@ -208,7 +211,7 @@ public function buildQuickForm() { * @return array|bool */ public static function formRule($fields, $files, $form) { - $errors = array(); + $errors = []; if (!empty($fields['option_label']) && !empty($fields['option_value']) && (count(array_filter($fields['option_label'])) == 0) && (count(array_filter($fields['option_value'])) == 0) @@ -357,7 +360,7 @@ public function postProcess() { $resultSetOptGrpId = $params['option_group_id']; } - $recontactInterval = array(); + $recontactInterval = []; if ($updateResultSet) { $optionValue = new CRM_Core_DAO_OptionValue(); $optionValue->option_group_id = $resultSetOptGrpId; @@ -409,23 +412,24 @@ public function postProcess() { if (!$this->_reportId && $survey->id && !empty($params['create_report'])) { $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); $activityStatus = array_flip($activityStatus); - $this->_params = array( + $this->_params = [ 'name' => "survey_{$survey->id}", 'title' => $params['report_title'] ? $params['report_title'] : $this->_values['title'], 'status_id_op' => 'eq', - 'status_id_value' => $activityStatus['Scheduled'], // reserved status - 'survey_id_value' => array($survey->id), + // reserved status + 'status_id_value' => $activityStatus['Scheduled'], + 'survey_id_value' => [$survey->id], 'description' => ts('Detailed report for canvassing, phone-banking, walk lists or other surveys.'), - ); + ]; //Default value of order by - $this->_params['order_bys'] = array( - 1 => array( + $this->_params['order_bys'] = [ + 1 => [ 'column' => 'sort_name', 'order' => 'ASC', - ), - ); + ], + ]; // for WalkList or default - $displayFields = array( + $displayFields = [ 'id', 'sort_name', 'result', @@ -433,37 +437,37 @@ public function postProcess() { 'street_name', 'street_unit', 'survey_response', - ); - if (CRM_Core_OptionGroup::getValue('activity_type', 'WalkList') == + ]; + if (CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'WalkList') == $this->_values['activity_type_id'] ) { - $this->_params['order_bys'] = array( - 1 => array( + $this->_params['order_bys'] = [ + 1 => [ 'column' => 'street_name', 'order' => 'ASC', - ), - 2 => array( + ], + 2 => [ 'column' => 'street_number_odd_even', 'order' => 'ASC', - ), - 3 => array( + ], + 3 => [ 'column' => 'street_number', 'order' => 'ASC', - ), - 4 => array( + ], + 4 => [ 'column' => 'sort_name', 'order' => 'ASC', - ), - ); + ], + ]; } - elseif (CRM_Core_OptionGroup::getValue('activity_type', 'PhoneBank') == + elseif (CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'PhoneBank') == $this->_values['activity_type_id'] ) { array_push($displayFields, 'phone'); } - elseif ((CRM_Core_OptionGroup::getValue('activity_type', 'Survey') == + elseif ((CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Survey') == $this->_values['activity_type_id']) || - (CRM_Core_OptionGroup::getValue('activity_type', 'Canvass') == + (CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Canvass') == $this->_values['activity_type_id']) ) { array_push($displayFields, 'phone', 'city', 'state_province_id', 'postal_code', 'email'); @@ -476,16 +480,16 @@ public function postProcess() { CRM_Report_Form_Instance::postProcess($this, FALSE); $query = "SELECT MAX(id) FROM civicrm_report_instance WHERE name = %1"; - $reportID = CRM_Core_DAO::singleValueQuery($query, array( - 1 => array( + $reportID = CRM_Core_DAO::singleValueQuery($query, [ + 1 => [ "survey_{$survey->id}", 'String', - ), - )); + ], + ]); if ($reportID) { $url = CRM_Utils_System::url("civicrm/report/instance/{$reportID}", 'reset=1'); $status = ts("A Survey Detail Report %2 has been created.", - array(1 => $url, 2 => $this->_params['title'])); + [1 => $url, 2 => $this->_params['title']]); } } diff --git a/CRM/Campaign/Form/Survey/TabHeader.php b/CRM/Campaign/Form/Survey/TabHeader.php index ee5c58c81e66..3aecece43a03 100644 --- a/CRM/Campaign/Form/Survey/TabHeader.php +++ b/CRM/Campaign/Form/Survey/TabHeader.php @@ -1,9 +1,9 @@ assign_by_ref('tabHeader', $tabs); CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') - ->addSetting(array( - 'tabSettings' => array( + ->addSetting([ + 'tabSettings' => [ 'active' => self::getCurrentTab($tabs), - ), - )); + ], + ]); return $tabs; } @@ -70,29 +70,29 @@ public static function process(&$form) { return NULL; } - $tabs = array( - 'main' => array( + $tabs = [ + 'main' => [ 'title' => ts('Main Information'), 'link' => NULL, 'valid' => FALSE, 'active' => FALSE, 'current' => FALSE, - ), - 'questions' => array( + ], + 'questions' => [ 'title' => ts('Questions'), 'link' => NULL, 'valid' => FALSE, 'active' => FALSE, 'current' => FALSE, - ), - 'results' => array( + ], + 'results' => [ 'title' => ts('Results'), 'link' => NULL, 'valid' => FALSE, 'active' => FALSE, 'current' => FALSE, - ), - ); + ], + ]; $surveyID = $form->getVar('_surveyId'); $class = $form->getVar('_name'); diff --git a/CRM/Campaign/Form/SurveyType.php b/CRM/Campaign/Form/SurveyType.php index 2d936f469af4..f7a38ad6c01e 100644 --- a/CRM/Campaign/Form/SurveyType.php +++ b/CRM/Campaign/Form/SurveyType.php @@ -1,9 +1,9 @@ $this->_gid); + $fieldValues = ['option_group_id' => $this->_gid]; $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue', $fieldValues); } @@ -127,9 +127,9 @@ public function buildQuickForm() { if ($this->_action == CRM_Core_Action::UPDATE && CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $this->_id, 'is_reserved') ) { - $this->freeze(array('label', 'is_active')); + $this->freeze(['label', 'is_active']); } - $this->add('text', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'weight'), TRUE); + $this->add('number', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'weight'), TRUE); $this->assign('id', $this->_id); } @@ -140,7 +140,7 @@ public function buildQuickForm() { public function postProcess() { if ($this->_action & CRM_Core_Action::DELETE) { - $fieldValues = array('option_group_id' => $this->_gid); + $fieldValues = ['option_group_id' => $this->_gid]; $wt = CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $this->_id, $fieldValues); if (CRM_Core_BAO_OptionValue::del($this->_id)) { @@ -148,7 +148,7 @@ public function postProcess() { } } else { - $params = $ids = array(); + $params = $ids = []; $params = $this->exportValues(); // set db value of filter in params if filter is non editable @@ -156,11 +156,10 @@ public function postProcess() { $params['filter'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'filter', 'id'); } - $groupParams = array('name' => ($this->_gName)); $params['component_id'] = CRM_Core_Component::getComponentID('CiviCampaign'); - $optionValue = CRM_Core_OptionValue::addOptionValue($params, $groupParams, $this->_action, $this->_id); + $optionValue = CRM_Core_OptionValue::addOptionValue($params, $this->_gName, $this->_action, $this->_id); - CRM_Core_Session::setStatus(ts('The Survey type \'%1\' has been saved.', array(1 => $optionValue->label)), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts('The Survey type \'%1\' has been saved.', [1 => $optionValue->label]), ts('Saved'), 'success'); } } diff --git a/CRM/Campaign/Form/Task.php b/CRM/Campaign/Form/Task.php index 7366d5d605b3..f6575049df61 100644 --- a/CRM/Campaign/Form/Task.php +++ b/CRM/Campaign/Form/Task.php @@ -1,9 +1,9 @@ _task, $campaignTasks); $this->assign('taskName', $taskName); - $ids = array(); + $ids = []; if ($values['radio_ts'] == 'ts_sel') { foreach ($values as $name => $value) { if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) { @@ -93,7 +65,7 @@ public function preProcess() { else { $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this); $cacheKey = "civicrm search {$qfKey}"; - $allCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey, "getall"); + $allCids = Civi::service('prevnext')->getSelection($cacheKey, "getall"); $ids = array_keys($allCids[$cacheKey]); $this->assign('totalSelectedVoters', count($ids)); } @@ -136,18 +108,17 @@ public function setContactIDs() { * @param bool $submitOnce */ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) { - $this->addButtons(array( - array( - 'type' => $nextType, - 'name' => $title, - 'isDefault' => TRUE, - ), - array( - 'type' => $backType, - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => $nextType, + 'name' => $title, + 'isDefault' => TRUE, + ], + [ + 'type' => $backType, + 'name' => ts('Cancel'), + ], + ]); } } diff --git a/CRM/Campaign/Form/Task/Interview.php b/CRM/Campaign/Form/Task/Interview.php index 8c0ce7992f8b..a1668fdb988f 100644 --- a/CRM/Campaign/Form/Task/Interview.php +++ b/CRM/Campaign/Form/Task/Interview.php @@ -1,9 +1,9 @@ _reserveToInterview = $this->get('reserveToInterview'); if ($this->_reserveToInterview || $this->_votingTab) { //user came from voting tab / reserve form. - foreach (array( - 'surveyId', - 'contactIds', - 'interviewerId', - ) as $fld) { + foreach ([ + 'surveyId', + 'contactIds', + 'interviewerId', + ] as $fld) { $this->{"_$fld"} = $this->get($fld); } //get the target voter ids. @@ -94,38 +95,39 @@ public function preProcess() { } if ($this->_surveyId) { - $params = array('id' => $this->_surveyId); + $params = ['id' => $this->_surveyId]; CRM_Campaign_BAO_Survey::retrieve($params, $this->_surveyDetails); } $orderClause = FALSE; $buttonName = $this->controller->getButtonName(); + $walkListActivityId = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'WalkList'); if ($buttonName == '_qf_Interview_submit_orderBy' && !empty($_POST['order_bys'])) { $orderByParams = CRM_Utils_Array::value('order_bys', $_POST); } - elseif (CRM_Core_OptionGroup::getValue('activity_type', 'WalkList') == $this->_surveyDetails['activity_type_id']) { + elseif ($walkListActivityId == $this->_surveyDetails['activity_type_id']) { $orderByParams - = array( - 1 => array( + = [ + 1 => [ 'column' => 'civicrm_address.street_name', 'order' => 'ASC', - ), - 2 => array( + ], + 2 => [ 'column' => 'civicrm_address.street_number%2', 'order' => 'ASC', - ), - 3 => array( + ], + 3 => [ 'column' => 'civicrm_address.street_number', 'order' => 'ASC', - ), - 4 => array( + ], + 4 => [ 'column' => 'contact_a.sort_name', 'order' => 'ASC', - ), - ); + ], + ]; } - $orderBy = array(); + $orderBy = []; if (!empty($orderByParams)) { foreach ($orderByParams as $key => $val) { if (!empty($val['column'])) { @@ -147,7 +149,7 @@ public function preProcess() { WHERE {$clause} {$orderClause}"; - $this->_contactIds = array(); + $this->_contactIds = []; $dao = CRM_Core_DAO::executeQuery($sql); while ($dao->fetch()) { $this->_contactIds[] = $dao->id; @@ -155,10 +157,10 @@ public function preProcess() { } //get the contact read only fields to display. - $readOnlyFields = array_merge(array( + $readOnlyFields = array_merge([ 'contact_type' => '', 'sort_name' => ts('Name'), - )); + ]); //get the read only field data. $returnProperties = array_fill_keys(array_keys($readOnlyFields), 1); @@ -170,10 +172,9 @@ public function preProcess() { $this->_contactIds, $this->_interviewerId ); - $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); - $scheduledStatusId = array_search('Scheduled', $activityStatus); + $scheduledStatusId = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Scheduled'); - $activityIds = array(); + $activityIds = []; foreach ($this->_contactIds as $key => $voterId) { $actVals = CRM_Utils_Array::value($voterId, $this->_surveyActivityIds); $statusId = CRM_Utils_Array::value('status_id', $actVals); @@ -216,9 +217,9 @@ public function preProcess() { //get the survey values. $this->_surveyValues = $this->get('surveyValues'); if (!is_array($this->_surveyValues)) { - $this->_surveyValues = array(); + $this->_surveyValues = []; if ($this->_surveyId) { - $surveyParams = array('id' => $this->_surveyId); + $surveyParams = ['id' => $this->_surveyId]; CRM_Campaign_BAO_Survey::retrieve($surveyParams, $this->_surveyValues); } $this->set('surveyValues', $this->_surveyValues); @@ -231,7 +232,7 @@ public function preProcess() { //get the survey result options. $this->_resultOptions = $this->get('resultOptions'); if (!is_array($this->_resultOptions)) { - $this->_resultOptions = array(); + $this->_resultOptions = []; if ($resultOptionId = CRM_Utils_Array::value('result_id', $this->_surveyValues)) { $this->_resultOptions = CRM_Core_OptionGroup::valuesByID($resultOptionId); } @@ -244,24 +245,24 @@ public function preProcess() { //append breadcrumb to survey dashboard. if (CRM_Campaign_BAO_Campaign::accessCampaign()) { $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'); - CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('Survey(s)'), 'url' => $url))); + CRM_Utils_System::appendBreadCrumb([['title' => ts('Survey(s)'), 'url' => $url]]); } //set the title. - $activityTypes = CRM_Core_PseudoConstant::activityType(FALSE, TRUE, FALSE, 'label', TRUE); $this->_surveyTypeId = CRM_Utils_Array::value('activity_type_id', $this->_surveyValues); - CRM_Utils_System::setTitle(ts('Record %1 Responses', array(1 => $activityTypes[$this->_surveyTypeId]))); + $surveyTypeLabel = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $this->_surveyTypeId); + CRM_Utils_System::setTitle(ts('Record %1 Responses', [1 => $surveyTypeLabel])); } public function validateIds() { - $required = array( + $required = [ 'surveyId' => ts('Could not find Survey.'), 'interviewerId' => ts('Could not find Interviewer.'), 'contactIds' => ts('No respondents are currently reserved for you to interview.'), 'resultOptions' => ts('Oops. It looks like there is no response option configured.'), - ); + ]; - $errorMessages = array(); + $errorMessages = []; foreach ($required as $fld => $msg) { if (empty($this->{"_$fld"})) { if (!$this->_votingTab) { @@ -282,19 +283,19 @@ public function buildQuickForm() { $this->assign('surveyTypeId', $this->_surveyTypeId); $options - = array( + = [ '' => ' - none - ', 'civicrm_address.street_name' => 'Street Name', 'civicrm_address.street_number%2' => 'Odd / Even Street Number', 'civicrm_address.street_number' => 'Street Number', 'contact_a.sort_name' => 'Respondent Name', - ); + ]; for ($i = 1; $i < count($options); $i++) { $this->addElement('select', "order_bys[{$i}][column]", ts('Order by Column'), $options); - $this->addElement('select', "order_bys[{$i}][order]", ts('Order by Order'), array( - 'ASC' => ts('Ascending'), - 'DESC' => ts('Descending'), - )); + $this->addElement('select', "order_bys[{$i}][order]", ts('Order by Order'), [ + 'ASC' => ts('Ascending'), + 'DESC' => ts('Descending'), + ]); } //pickup the uf fields. @@ -313,9 +314,9 @@ public function buildQuickForm() { //build the result field. if (!empty($this->_resultOptions)) { $this->add('select', "field[$contactId][result]", ts('Result'), - array( + [ '' => ts('- select -'), - ) + + ] + array_combine($this->_resultOptions, $this->_resultOptions) ); } @@ -326,7 +327,7 @@ public function buildQuickForm() { if ($this->_allowAjaxReleaseButton) { $this->addElement('hidden', "field[{$contactId}][is_release_or_reserve]", 0, - array('id' => "field_{$contactId}_is_release_or_reserve") + ['id' => "field_{$contactId}_is_release_or_reserve"] ); } } @@ -337,20 +338,20 @@ public function buildQuickForm() { return; } - $buttons = array( - array( + $buttons = [ + [ 'type' => 'cancel', 'name' => ts('Done'), 'subName' => 'interview', 'isDefault' => TRUE, - ), - ); + ], + ]; - $buttons[] = array( + $buttons[] = [ 'type' => 'submit', 'name' => ts('Order By >>'), 'subName' => 'orderBy', - ); + ]; $manageCampaign = CRM_Core_Permission::check('manage campaign'); $adminCampaign = CRM_Core_Permission::check('administer CiviCampaign'); @@ -358,21 +359,21 @@ public function buildQuickForm() { $adminCampaign || CRM_Core_Permission::check('release campaign contacts') ) { - $buttons[] = array( + $buttons[] = [ 'type' => 'next', 'name' => ts('Release Respondents >>'), 'subName' => 'interviewToRelease', - ); + ]; } if ($manageCampaign || $adminCampaign || CRM_Core_Permission::check('reserve campaign contacts') ) { - $buttons[] = array( + $buttons[] = [ 'type' => 'done', 'name' => ts('Reserve More Respondents >>'), 'subName' => 'interviewToReserve', - ); + ]; } $this->addButtons($buttons); @@ -383,11 +384,11 @@ public function buildQuickForm() { */ public function setDefaultValues() { //load default data for only contact fields. - $contactFields = $defaults = array(); + $contactFields = $defaults = []; foreach ($this->_surveyFields as $name => $field) { $acceptable_types = CRM_Contact_BAO_ContactType::basicTypes(); $acceptable_types[] = 'Contact'; - if (in_array($field['field_type'], $acceptable_types)) { + if (isset($field['field_type']) && (in_array($field['field_type'], $acceptable_types))) { $contactFields[$name] = $field; } } @@ -397,35 +398,36 @@ public function setDefaultValues() { } } - if (CRM_Core_OptionGroup::getValue('activity_type', 'WalkList') == $this->_surveyDetails['activity_type_id']) { + $walkListActivityId = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'WalkList'); + if ($walkListActivityId == $this->_surveyDetails['activity_type_id']) { $defaults['order_bys'] - = array( - 1 => array( + = [ + 1 => [ 'column' => 'civicrm_address.street_name', 'order' => 'ASC', - ), - 2 => array( + ], + 2 => [ 'column' => 'civicrm_address.street_number%2', 'order' => 'ASC', - ), - 3 => array( + ], + 3 => [ 'column' => 'civicrm_address.street_number', 'order' => 'ASC', - ), - 4 => array( + ], + 4 => [ 'column' => 'contact_a.sort_name', 'order' => 'ASC', - ), - ); + ], + ]; } else { $defaults['order_bys'] - = array( - 1 => array( + = [ + 1 => [ 'column' => 'contact_a.sort_name', 'order' => 'ASC', - ), - ); + ], + ]; } return $defaults; } @@ -441,11 +443,11 @@ public function postProcess() { } elseif ($buttonName == '_qf_Interview_next_interviewToRelease') { //get ready to jump to release form. - foreach (array( - 'surveyId', - 'contactIds', - 'interviewerId', - ) as $fld) { + foreach ([ + 'surveyId', + 'contactIds', + 'interviewerId', + ] as $fld) { $this->controller->set($fld, $this->{"_$fld"}); } $this->controller->set('interviewToRelease', TRUE); @@ -480,7 +482,7 @@ public static function registerInterview($params) { static $statusId; if (!$statusId) { - $statusId = array_search('Completed', CRM_Core_PseudoConstant::activityStatus('name')); + $statusId = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Completed'); } //format custom fields. @@ -492,9 +494,9 @@ public static function registerInterview($params) { CRM_Core_BAO_CustomValueTable::store($customParams, 'civicrm_activity', $activityId); //process contact data. - $contactParams = $fields = array(); + $contactParams = $fields = []; - $contactFieldTypes = array_merge(array('Contact'), CRM_Contact_BAO_ContactType::basicTypes()); + $contactFieldTypes = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes()); $responseFields = CRM_Campaign_BAO_Survey::getSurveyResponseFields($params['survey_id']); if (!empty($responseFields)) { foreach ($params as $key => $value) { @@ -537,7 +539,7 @@ public static function registerInterview($params) { $subject .= ts('Respondent Interview'); $activity->subject = $subject; - $activityParams = array( + $activityParams = [ 'details' => 'details', 'result' => 'result', 'engagement_level' => 'activity_engagement_level', @@ -547,7 +549,7 @@ public static function registerInterview($params) { 'location' => 'activity_location', 'campaign_id' => 'activity_campaign_id', 'duration' => 'activity_duration', - ); + ]; foreach ($activityParams as $key => $field) { if (!empty($params[$field])) { $activity->$key = $params[$field]; @@ -558,7 +560,6 @@ public static function registerInterview($params) { //really this should use Activity BAO& not be here but refactoring will have to be later //actually the whole ajax call could be done as an api ajax call & post hook would be sorted CRM_Utils_Hook::post('edit', 'Activity', $activity->id, $activity); - $activity->free(); return $activityId; } @@ -580,16 +581,12 @@ public function getVoterIds() { $this->_contactIds = $this->get('contactIds'); if (!is_array($this->_contactIds)) { //get the survey activities. - $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); - $statusIds = array(); - if ($statusId = array_search('Scheduled', $activityStatus)) { - $statusIds[] = $statusId; - } + $statusIds[] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Scheduled'); $surveyActivities = CRM_Campaign_BAO_Survey::getSurveyVoterInfo($this->_surveyId, $this->_interviewerId, $statusIds ); - $this->_contactIds = array(); + $this->_contactIds = []; foreach ($surveyActivities as $val) { $this->_contactIds[$val['voter_id']] = $val['voter_id']; } @@ -639,7 +636,7 @@ public function filterVoterIds() { INNER JOIN {$tempTableName} ON ( {$tempTableName}.survey_contact_id = contact.id ) WHERE contact.contact_type != %1"; $removeContact = CRM_Core_DAO::executeQuery($query, - array(1 => array($profileType, 'String')) + [1 => [$profileType, 'String']] ); while ($removeContact->fetch()) { unset($this->_contactIds[$removeContact->id]); diff --git a/CRM/Campaign/Form/Task/Print.php b/CRM/Campaign/Form/Task/Print.php index 6fc4c94f1e34..bfc08a11000f 100644 --- a/CRM/Campaign/Form/Task/Print.php +++ b/CRM/Campaign/Form/Task/Print.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Print Respondents'), - 'js' => array('onclick' => 'window.print()'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Done'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Print Respondents'), + 'js' => ['onclick' => 'window.print()'], + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Done'), + ], + ]); } /** diff --git a/CRM/Campaign/Form/Task/Release.php b/CRM/Campaign/Form/Task/Release.php index 9f1d0cdcd3c9..75d994d1557c 100644 --- a/CRM/Campaign/Form/Task/Release.php +++ b/CRM/Campaign/Form/Task/Release.php @@ -1,9 +1,9 @@ _interviewToRelease = $this->get('interviewToRelease'); if ($this->_interviewToRelease) { //user came from interview form. - foreach (array( - 'surveyId', - 'contactIds', - 'interviewerId', - ) as $fld) { + foreach ([ + 'surveyId', + 'contactIds', + 'interviewerId', + ] as $fld) { $this->{"_$fld"} = $this->get($fld); } @@ -95,15 +95,13 @@ public function preProcess() { CRM_Core_Error::statusBounce(ts('Could not find respondents to release.')); } - $surveyDetails = array(); - $params = array('id' => $this->_surveyId); + $surveyDetails = []; + $params = ['id' => $this->_surveyId]; $this->_surveyDetails = CRM_Campaign_BAO_Survey::retrieve($params, $surveyDetails); $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); - $statusIds = array(); - foreach (array( - 'Scheduled', - ) as $name) { + $statusIds = []; + foreach (['Scheduled'] as $name) { if ($statusId = array_search($name, $activityStatus)) { $statusIds[] = $statusId; } @@ -123,7 +121,7 @@ public function preProcess() { //append breadcrumb to survey dashboard. if (CRM_Campaign_BAO_Campaign::accessCampaign()) { $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'); - CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('Survey(s)'), 'url' => $url))); + CRM_Utils_System::appendBreadCrumb([['title' => ts('Survey(s)'), 'url' => $url]]); } //set the title. @@ -139,7 +137,7 @@ public function buildQuickForm() { } public function postProcess() { - $deleteActivityIds = array(); + $deleteActivityIds = []; foreach ($this->_contactIds as $cid) { if (array_key_exists($cid, $this->_surveyActivities)) { $deleteActivityIds[] = $this->_surveyActivities[$cid]['activity_id']; @@ -152,19 +150,19 @@ public function postProcess() { CRM_Core_DAO::executeQuery($query); if ($deleteActivityIds) { - $status = ts("Respondent has been released.", array( + $status = ts("Respondent has been released.", [ 'count' => count($deleteActivityIds), 'plural' => '%count respondents have been released.', - )); + ]); CRM_Core_Session::setStatus($status, ts('Released'), 'success'); } if (count($this->_contactIds) > count($deleteActivityIds)) { $status = ts('1 respondent did not release.', - array( + [ 'count' => (count($this->_contactIds) - count($deleteActivityIds)), 'plural' => '%count respondents did not release.', - ) + ] ); CRM_Core_Session::setStatus($status, ts('Notice'), 'alert'); } diff --git a/CRM/Campaign/Form/Task/Reserve.php b/CRM/Campaign/Form/Task/Reserve.php index 6638ee1950ea..639156c16451 100644 --- a/CRM/Campaign/Form/Task/Reserve.php +++ b/CRM/Campaign/Form/Task/Reserve.php @@ -1,9 +1,9 @@ $this->_surveyId); + $params = ['id' => $this->_surveyId]; CRM_Campaign_BAO_Survey::retrieve($params, $this->_surveyDetails); //get the survey activities. $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); - $statusIds = array(); - foreach (array('Scheduled') as $name) { + $statusIds = []; + foreach (['Scheduled'] as $name) { if ($statusId = array_search($name, $activityStatus)) { $statusIds[] = $statusId; } @@ -111,7 +111,7 @@ public function preProcess() { //append breadcrumb to survey dashboard. if (CRM_Campaign_BAO_Campaign::accessCampaign()) { $url = CRM_Utils_System::url('civicrm/campaign', 'reset=1&subPage=survey'); - CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('Survey(s)'), 'url' => $url))); + CRM_Utils_System::appendBreadCrumb([['title' => ts('Survey(s)'), 'url' => $url]]); } //set the title. @@ -127,10 +127,10 @@ public function validateSurvey() { } elseif (count($this->_contactIds) > ($maxVoters - $this->_numVoters)) { $errorMsg = ts('You can reserve a maximum of %count contact at a time for this survey.', - array( + [ 'plural' => 'You can reserve a maximum of %count contacts at a time for this survey.', 'count' => $maxVoters - $this->_numVoters, - ) + ] ); } } @@ -138,10 +138,10 @@ public function validateSurvey() { $defaultNum = CRM_Utils_Array::value('default_number_of_contacts', $this->_surveyDetails); if (!$errorMsg && $defaultNum && (count($this->_contactIds) > $defaultNum)) { $errorMsg = ts('You can reserve a maximum of %count contact at a time for this survey.', - array( + [ 'plural' => 'You can reserve a maximum of %count contacts at a time for this survey.', 'count' => $defaultNum, - ) + ] ); } @@ -163,37 +163,37 @@ public function buildQuickForm() { if (is_array($groups) && !empty($groups)) { $hasExistingGroups = TRUE; $this->addElement('select', 'groups', ts('Add respondent(s) to existing group(s)'), - $groups, array('multiple' => "multiple", 'class' => 'crm-select2') + $groups, ['multiple' => "multiple", 'class' => 'crm-select2'] ); } $this->assign('hasExistingGroups', $hasExistingGroups); - $buttons = array( - array( + $buttons = [ + [ 'type' => 'done', 'name' => ts('Reserve'), 'subName' => 'reserve', 'isDefault' => TRUE, - ), - ); + ], + ]; if (CRM_Core_Permission::check('manage campaign') || CRM_Core_Permission::check('administer CiviCampaign') || CRM_Core_Permission::check('interview campaign contacts') ) { - $buttons[] = array( + $buttons[] = [ 'type' => 'next', 'name' => ts('Reserve and Interview'), 'subName' => 'reserveToInterview', - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'back', 'name' => ts('Cancel'), - ); + ]; $this->addButtons($buttons); - $this->addFormRule(array('CRM_Campaign_Form_Task_Reserve', 'formRule'), $this); + $this->addFormRule(['CRM_Campaign_Form_Task_Reserve', 'formRule'], $this); } /** @@ -209,19 +209,19 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; $invalidGroupName = FALSE; if (!empty($fields['newGroupName'])) { $title = trim($fields['newGroupName']); $name = CRM_Utils_String::titleToVar($title); $query = 'select count(*) from civicrm_group where name like %1 OR title like %2'; - $grpCnt = CRM_Core_DAO::singleValueQuery($query, array( - 1 => array($name, 'String'), - 2 => array($title, 'String'), - )); + $grpCnt = CRM_Core_DAO::singleValueQuery($query, [ + 1 => [$name, 'String'], + 2 => [$title, 'String'], + ]); if ($grpCnt) { $invalidGroupName = TRUE; - $errors['newGroupName'] = ts('Group \'%1\' already exists.', array(1 => $fields['newGroupName'])); + $errors['newGroupName'] = ts('Group \'%1\' already exists.', [1 => $fields['newGroupName']]); } } $self->assign('invalidGroupName', $invalidGroupName); @@ -239,14 +239,14 @@ public function postProcess() { $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); $statusHeld = array_search('Scheduled', $activityStatus); - $reservedVoterIds = array(); + $reservedVoterIds = []; foreach ($this->_contactIds as $cid) { $subject = $this->_surveyDetails['title'] . ' - ' . ts('Respondent Reservation'); $session = CRM_Core_Session::singleton(); - $activityParams = array( + $activityParams = [ 'source_contact_id' => $session->get('userID'), - 'assignee_contact_id' => array($this->_interviewerId), - 'target_contact_id' => array($cid), + 'assignee_contact_id' => [$this->_interviewerId], + 'target_contact_id' => [$cid], 'source_record_id' => $this->_surveyId, 'activity_type_id' => $this->_surveyDetails['activity_type_id'], 'subject' => $subject, @@ -254,7 +254,7 @@ public function postProcess() { 'status_id' => $statusHeld, 'skipRecentView' => 1, 'campaign_id' => CRM_Utils_Array::value('campaign_id', $this->_surveyDetails), - ); + ]; $activity = CRM_Activity_BAO_Activity::create($activityParams); if ($activity->id) { $countVoters++; @@ -270,10 +270,10 @@ public function postProcess() { // Success message if ($countVoters > 0) { - $status = '

    ' . ts("%count contact has been reserved.", array('plural' => '%count contacts have been reserved.', 'count' => $countVoters)) . '

    '; + $status = '

    ' . ts("%count contact has been reserved.", ['plural' => '%count contacts have been reserved.', 'count' => $countVoters]) . '

    '; if ($groupAdditions) { $status .= '

    ' . ts('They have been added to %1.', - array(1 => implode(' ' . ts('and') . ' ', $groupAdditions)) + [1 => implode(' ' . ts('and') . ' ', $groupAdditions)] ) . '

    '; } CRM_Core_Session::setStatus($status, ts('Reservation Added'), 'success'); @@ -281,10 +281,10 @@ public function postProcess() { // Error message if (count($this->_contactIds) > $countVoters) { CRM_Core_Session::setStatus(ts('Reservation did not add for %count contact.', - array( + [ 'plural' => 'Reservation did not add for %count contacts.', 'count' => (count($this->_contactIds) - $countVoters), - ) + ] ), ts('Notice')); } @@ -306,24 +306,24 @@ public function postProcess() { * @return array */ private function _addRespondentToGroup($contactIds) { - $groupAdditions = array(); + $groupAdditions = []; if (empty($contactIds)) { return $groupAdditions; } $params = $this->controller->exportValues($this->_name); - $groups = CRM_Utils_Array::value('groups', $params, array()); + $groups = CRM_Utils_Array::value('groups', $params, []); $newGroupName = CRM_Utils_Array::value('newGroupName', $params); $newGroupDesc = CRM_Utils_Array::value('newGroupDesc', $params); $newGroupId = NULL; //create new group. if ($newGroupName) { - $grpParams = array( + $grpParams = [ 'title' => $newGroupName, 'description' => $newGroupDesc, 'is_active' => TRUE, - ); + ]; $group = CRM_Contact_BAO_Group::create($grpParams); $groups[] = $newGroupId = $group->id; } diff --git a/CRM/Campaign/Form/Task/Result.php b/CRM/Campaign/Form/Task/Result.php index 98bd9fb73124..25f81fb7fdbd 100644 --- a/CRM/Campaign/Form/Task/Result.php +++ b/CRM/Campaign/Form/Task/Result.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Campaign/Info.php b/CRM/Campaign/Info.php index 8667ca30c38c..9543c059d5b5 100644 --- a/CRM/Campaign/Info.php +++ b/CRM/Campaign/Info.php @@ -1,9 +1,9 @@ 'CiviCampaign', 'translatedName' => ts('CiviCampaign'), 'title' => ts('CiviCRM Campaign Engine'), 'search' => 1, 'showActivitiesInCore' => 1, - ); + ]; } - /** * @inheritDoc * @param bool $getAllUnconditionally @@ -64,35 +64,35 @@ public function getInfo() { * @return array */ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { - $permissions = array( - 'administer CiviCampaign' => array( + $permissions = [ + 'administer CiviCampaign' => [ ts('administer CiviCampaign'), ts('Create new campaign, survey and petition types and their status'), - ), - 'manage campaign' => array( + ], + 'manage campaign' => [ ts('manage campaign'), ts('Create new campaigns, surveys and petitions, reserve respondents'), - ), - 'reserve campaign contacts' => array( + ], + 'reserve campaign contacts' => [ ts('reserve campaign contacts'), ts('Reserve campaign contacts for surveys and petitions'), - ), - 'release campaign contacts' => array( + ], + 'release campaign contacts' => [ ts('release campaign contacts'), ts('Release reserved campaign contacts for surveys and petitions'), - ), - 'interview campaign contacts' => array( + ], + 'interview campaign contacts' => [ ts('interview campaign contacts'), ts('Record survey and petition responses from their reserved contacts'), - ), - 'gotv campaign contacts' => array( + ], + 'gotv campaign contacts' => [ ts('GOTV campaign contacts'), ts('Record that contacts voted'), - ), - 'sign CiviCRM Petition' => array( + ], + 'sign CiviCRM Petition' => [ ts('sign CiviCRM Petition'), - ), - ); + ], + ]; if (!$descriptions) { foreach ($permissions as $name => $attr) { @@ -103,7 +103,6 @@ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = F return $permissions; } - /** * @inheritDoc * @return null @@ -130,6 +129,14 @@ public function registerTab() { return NULL; } + /** + * @inheritDoc + * @return string + */ + public function getIcon() { + return 'crm-i fa-star-o'; + } + /** * @inheritDoc * @return null @@ -154,20 +161,20 @@ public function creatNewShortcut(&$shortCuts) { if (CRM_Core_Permission::check('manage campaign') || CRM_Core_Permission::check('administer CiviCampaign') ) { - $shortCuts = array_merge($shortCuts, array( - array( + $shortCuts = array_merge($shortCuts, [ + [ 'path' => 'civicrm/campaign/add', 'query' => "reset=1&action=add", 'ref' => 'new-campaign', 'title' => ts('Campaign'), - ), - array( + ], + [ 'path' => 'civicrm/survey/add', 'query' => "reset=1&action=add", 'ref' => 'new-survey', 'title' => ts('Survey'), - ), - )); + ], + ]); } } diff --git a/CRM/Campaign/Page/AJAX.php b/CRM/Campaign/Page/AJAX.php index caaadfad8a08..ecec01993619 100644 --- a/CRM/Campaign/Page/AJAX.php +++ b/CRM/Campaign/Page/AJAX.php @@ -1,9 +1,9 @@ 'fail', 'voter_id' => $voterId, 'activity_id' => $params['interviewer_id'], - ); + ]; //time to validate custom data. $errors = CRM_Core_BAO_CustomField::validateCustomData($params); @@ -108,7 +108,7 @@ public static function loadOptionGroupDetails() { $id = CRM_Utils_Request::retrieve('option_group_id', 'Integer', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST'); $status = 'fail'; - $opValues = array(); + $opValues = []; if ($id) { $groupParams['id'] = $id; @@ -136,10 +136,10 @@ public static function loadOptionGroupDetails() { $status = 'success'; } - $result = array( + $result = [ 'status' => $status, 'result' => $opValues, - ); + ]; CRM_Utils_JSON::output($result); } @@ -149,7 +149,7 @@ public function voterList() { $searchCriteria = CRM_Utils_Request::retrieve('searchCriteria', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST'); $searchParams = explode(',', $searchCriteria); - $params = $searchRows = array(); + $params = $searchRows = []; foreach ($searchParams as $param) { if (!empty($_POST[$param])) { $params[$param] = $_POST[$param]; @@ -157,10 +157,10 @@ public function voterList() { } //format multi-select group and contact types. - foreach (array( - 'group', - 'contact_type', - ) as $param) { + foreach ([ + 'group', + 'contact_type', + ] as $param) { $paramValue = CRM_Utils_Array::value($param, $params); if ($paramValue) { unset($params[$param]); @@ -171,12 +171,12 @@ public function voterList() { } } - $voterClauseParams = array(); - foreach (array( - 'campaign_survey_id', - 'survey_interviewer_id', - 'campaign_search_voter_for', - ) as $fld) { + $voterClauseParams = []; + foreach ([ + 'campaign_survey_id', + 'survey_interviewer_id', + 'campaign_search_voter_for', + ] as $fld) { $voterClauseParams[$fld] = CRM_Utils_Array::value($fld, $params); } @@ -224,42 +224,42 @@ public function voterList() { } } - $selectorCols = array( + $selectorCols = [ 'sort_name', 'street_address', 'street_name', 'street_number', 'street_unit', - ); + ]; // get the data table params. - $dataTableParams = array( - 'sEcho' => array( + $dataTableParams = [ + 'sEcho' => [ 'name' => 'sEcho', 'type' => 'Integer', 'default' => 0, - ), - 'offset' => array( + ], + 'offset' => [ 'name' => 'iDisplayStart', 'type' => 'Integer', 'default' => 0, - ), - 'rowCount' => array( + ], + 'rowCount' => [ 'name' => 'iDisplayLength', 'type' => 'Integer', 'default' => 25, - ), - 'sort' => array( + ], + 'sort' => [ 'name' => 'iSortCol_0', 'type' => 'Integer', 'default' => 'sort_name', - ), - 'sortOrder' => array( + ], + 'sortOrder' => [ 'name' => 'sSortDir_0', 'type' => 'String', 'default' => 'asc', - ), - ); + ], + ]; foreach ($dataTableParams as $pName => $pValues) { $$pName = $pValues['default']; if (!empty($_POST[$pValues['name']])) { @@ -291,14 +291,14 @@ public function voterList() { $iTotal = $searchCount; - $selectorCols = array( + $selectorCols = [ 'contact_type', 'sort_name', 'street_address', 'street_name', 'street_number', 'street_unit', - ); + ]; $extraVoterColName = 'is_interview_conducted'; if ($params['campaign_search_voter_for'] == 'reserve') { @@ -328,7 +328,7 @@ public function voterList() { $result->contact_id ); - $searchRows[$contactID] = array('id' => $contactID); + $searchRows[$contactID] = ['id' => $contactID]; foreach ($selectorCols as $col) { $val = $result->$col; if ($col == 'contact_type') { @@ -357,7 +357,7 @@ public function voterList() { } } - $selectorElements = array_merge($selectorCols, array($extraVoterColName)); + $selectorElements = array_merge($selectorCols, [$extraVoterColName]); $iFilteredTotal = $iTotal; @@ -400,13 +400,13 @@ public function processVoterData() { } } if ($createActivity) { - $ids = array( + $ids = [ 'source_record_id', 'source_contact_id', 'target_contact_id', 'assignee_contact_id', - ); - $activityParams = array(); + ]; + $activityParams = []; foreach ($ids as $id) { $val = CRM_Utils_Array::value($id, $_POST); if (!$val) { @@ -421,12 +421,12 @@ public function processVoterData() { $activityStatus = CRM_Core_PseudoConstant::activityStatus('name'); $scheduledStatusId = array_search('Scheduled', $activityStatus); if ($isReserved) { - $surveyValues = array(); - $surveyParams = array('id' => $activityParams['source_record_id']); + $surveyValues = []; + $surveyParams = ['id' => $activityParams['source_record_id']]; CRM_Core_DAO::commonRetrieve('CRM_Campaign_DAO_Survey', $surveyParams, $surveyValues, - array('title', 'activity_type_id', 'campaign_id') + ['title', 'activity_type_id', 'campaign_id'] ); $activityTypeId = $surveyValues['activity_type_id']; @@ -451,11 +451,11 @@ public function processVoterData() { } else { //delete reserved activity for given voter. - $voterIds = array($activityParams['target_contact_id']); + $voterIds = [$activityParams['target_contact_id']]; $activities = CRM_Campaign_BAO_Survey::voterActivityDetails($activityParams['source_record_id'], $voterIds, $activityParams['source_contact_id'], - array($scheduledStatusId) + [$scheduledStatusId] ); foreach ($activities as $voterId => $values) { $activityId = CRM_Utils_Array::value('activity_id', $values); @@ -491,47 +491,14 @@ public function processVoterData() { } } - CRM_Utils_JSON::output(array('status' => $status)); - } - - public function allActiveCampaigns() { - $currentCampaigns = CRM_Campaign_BAO_Campaign::getCampaigns(); - $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(NULL, NULL, TRUE, FALSE, TRUE); - $options = array( - array( - 'value' => '', - 'title' => ts('- select -'), - ), - ); - foreach ($campaigns as $value => $title) { - $class = NULL; - if (!array_key_exists($value, $currentCampaigns)) { - $class = 'status-past'; - } - $options[] = array( - 'value' => $value, - 'title' => $title, - 'class' => $class, - ); - } - $status = 'fail'; - if (count($options) > 1) { - $status = 'success'; - } - - $results = array( - 'status' => $status, - 'campaigns' => $options, - ); - - CRM_Utils_JSON::output($results); + CRM_Utils_JSON::output(['status' => $status]); } public function campaignGroups() { $surveyId = CRM_Utils_Request::retrieve('survey_id', 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST' ); - $campGroups = array(); + $campGroups = []; if ($surveyId) { $campaignId = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'campaign_id'); if ($campaignId) { @@ -544,22 +511,22 @@ public function campaignGroups() { if (empty($campGroups)) { $campGroups = CRM_Core_PseudoConstant::group(); } - $groups = array( - array( + $groups = [ + [ 'value' => '', 'title' => ts('- select -'), - ), - ); + ], + ]; foreach ($campGroups as $grpId => $title) { - $groups[] = array( + $groups[] = [ 'value' => $grpId, 'title' => $title, - ); + ]; } - $results = array( + $results = [ 'status' => 'success', 'groups' => $groups, - ); + ]; CRM_Utils_JSON::output($results); } @@ -573,7 +540,7 @@ public static function campaignList() { $searchCriteria = CRM_Utils_Request::retrieve('searchCriteria', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST'); $searchParams = explode(',', $searchCriteria); - $params = $searchRows = array(); + $params = $searchRows = []; foreach ($searchParams as $param) { if (isset($_POST[$param])) { $params[$param] = $_POST[$param]; @@ -581,7 +548,7 @@ public static function campaignList() { } //this is sequence columns on datatable. - $selectorCols = array( + $selectorCols = [ 'id', 'name', 'title', @@ -595,36 +562,36 @@ public static function campaignList() { 'is_active', 'isActive', 'action', - ); + ]; // get the data table params. - $dataTableParams = array( - 'sEcho' => array( + $dataTableParams = [ + 'sEcho' => [ 'name' => 'sEcho', 'type' => 'Integer', 'default' => 0, - ), - 'offset' => array( + ], + 'offset' => [ 'name' => 'iDisplayStart', 'type' => 'Integer', 'default' => 0, - ), - 'rowCount' => array( + ], + 'rowCount' => [ 'name' => 'iDisplayLength', 'type' => 'Integer', 'default' => 25, - ), - 'sort' => array( + ], + 'sort' => [ 'name' => 'iSortCol_0', 'type' => 'Integer', 'default' => 'start_date', - ), - 'sortOrder' => array( + ], + 'sortOrder' => [ 'name' => 'sSortDir_0', 'type' => 'String', 'default' => 'desc', - ), - ); + ], + ]; foreach ($dataTableParams as $pName => $pValues) { $$pName = $pValues['default']; if (!empty($_POST[$pValues['name']])) { @@ -634,12 +601,12 @@ public static function campaignList() { } } } - foreach (array( - 'sort', - 'offset', - 'rowCount', - 'sortOrder', - ) as $sortParam) { + foreach ([ + 'sort', + 'offset', + 'rowCount', + 'sortOrder', + ] as $sortParam) { $params[$sortParam] = $$sortParam; } @@ -676,7 +643,7 @@ public function surveyList() { $searchCriteria = CRM_Utils_Request::retrieve('searchCriteria', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST'); $searchParams = explode(',', $searchCriteria); - $params = $searchRows = array(); + $params = $searchRows = []; foreach ($searchParams as $param) { if (!empty($_POST[$param])) { $params[$param] = $_POST[$param]; @@ -684,7 +651,7 @@ public function surveyList() { } //this is sequence columns on datatable. - $selectorCols = array( + $selectorCols = [ 'id', 'title', 'campaign_id', @@ -700,36 +667,36 @@ public function surveyList() { 'result_id', 'action', 'voterLinks', - ); + ]; // get the data table params. - $dataTableParams = array( - 'sEcho' => array( + $dataTableParams = [ + 'sEcho' => [ 'name' => 'sEcho', 'type' => 'Integer', 'default' => 0, - ), - 'offset' => array( + ], + 'offset' => [ 'name' => 'iDisplayStart', 'type' => 'Integer', 'default' => 0, - ), - 'rowCount' => array( + ], + 'rowCount' => [ 'name' => 'iDisplayLength', 'type' => 'Integer', 'default' => 25, - ), - 'sort' => array( + ], + 'sort' => [ 'name' => 'iSortCol_0', 'type' => 'Integer', 'default' => 'created_date', - ), - 'sortOrder' => array( + ], + 'sortOrder' => [ 'name' => 'sSortDir_0', 'type' => 'String', 'default' => 'desc', - ), - ); + ], + ]; foreach ($dataTableParams as $pName => $pValues) { $$pName = $pValues['default']; if (!empty($_POST[$pValues['name']])) { @@ -739,12 +706,12 @@ public function surveyList() { } } } - foreach (array( - 'sort', - 'offset', - 'rowCount', - 'sortOrder', - ) as $sortParam) { + foreach ([ + 'sort', + 'offset', + 'rowCount', + 'sortOrder', + ] as $sortParam) { $params[$sortParam] = $$sortParam; } @@ -781,7 +748,7 @@ public function petitionList() { $searchCriteria = CRM_Utils_Request::retrieve('searchCriteria', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'POST'); $searchParams = explode(',', $searchCriteria); - $params = $searchRows = array(); + $params = $searchRows = []; foreach ($searchParams as $param) { if (!empty($_POST[$param])) { $params[$param] = $_POST[$param]; @@ -789,7 +756,7 @@ public function petitionList() { } //this is sequence columns on datatable. - $selectorCols = array( + $selectorCols = [ 'id', 'title', 'campaign_id', @@ -800,36 +767,36 @@ public function petitionList() { 'is_active', 'isActive', 'action', - ); + ]; // get the data table params. - $dataTableParams = array( - 'sEcho' => array( + $dataTableParams = [ + 'sEcho' => [ 'name' => 'sEcho', 'type' => 'Integer', 'default' => 0, - ), - 'offset' => array( + ], + 'offset' => [ 'name' => 'iDisplayStart', 'type' => 'Integer', 'default' => 0, - ), - 'rowCount' => array( + ], + 'rowCount' => [ 'name' => 'iDisplayLength', 'type' => 'Integer', 'default' => 25, - ), - 'sort' => array( + ], + 'sort' => [ 'name' => 'iSortCol_0', 'type' => 'Integer', 'default' => 'created_date', - ), - 'sortOrder' => array( + ], + 'sortOrder' => [ 'name' => 'sSortDir_0', 'type' => 'String', 'default' => 'desc', - ), - ); + ], + ]; foreach ($dataTableParams as $pName => $pValues) { $$pName = $pValues['default']; if (!empty($_POST[$pValues['name']])) { @@ -839,12 +806,12 @@ public function petitionList() { } } } - foreach (array( - 'sort', - 'offset', - 'rowCount', - 'sortOrder', - ) as $sortParam) { + foreach ([ + 'sort', + 'offset', + 'rowCount', + 'sortOrder', + ] as $sortParam) { $params[$sortParam] = $$sortParam; } diff --git a/CRM/Campaign/Page/DashBoard.php b/CRM/Campaign/Page/DashBoard.php index 0ba913f40835..08ac5a08e228 100644 --- a/CRM/Campaign/Page/DashBoard.php +++ b/CRM/Campaign/Page/DashBoard.php @@ -1,9 +1,9 @@ array( + self::$_campaignActionLinks = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/campaign/add', 'qs' => 'reset=1&action=update&id=%%id%%', 'title' => ts('Update Campaign'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'title' => ts('Disable Campaign'), 'ref' => 'crm-enable-disable', - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'title' => ts('Enable Campaign'), 'ref' => 'crm-enable-disable', - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/campaign/add', 'qs' => 'action=delete&reset=1&id=%%id%%', 'title' => ts('Delete Campaign'), - ), - ); + ], + ]; } return self::$_campaignActionLinks; @@ -88,30 +88,30 @@ public static function campaignActionLinks() { public static function surveyActionLinks() { // check if variable _actionsLinks is populated if (!isset(self::$_surveyActionLinks)) { - self::$_surveyActionLinks = array( - CRM_Core_Action::UPDATE => array( + self::$_surveyActionLinks = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/survey/configure/main', 'qs' => 'action=update&id=%%id%%&reset=1', 'title' => ts('Update Survey'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable Survey'), - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable Survey'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/survey/delete', 'qs' => 'id=%%id%%&reset=1', 'title' => ts('Delete Survey'), - ), - ); + ], + ]; } return self::$_surveyActionLinks; @@ -123,43 +123,43 @@ public static function surveyActionLinks() { public static function petitionActionLinks() { if (!isset(self::$_petitionActionLinks)) { self::$_petitionActionLinks = self::surveyActionLinks(); - self::$_petitionActionLinks[CRM_Core_Action::UPDATE] = array( + self::$_petitionActionLinks[CRM_Core_Action::UPDATE] = [ 'name' => ts('Edit'), 'url' => 'civicrm/petition/add', 'qs' => 'action=update&id=%%id%%&reset=1', 'title' => ts('Update Petition'), - ); - self::$_petitionActionLinks[CRM_Core_Action::DISABLE] = array( + ]; + self::$_petitionActionLinks[CRM_Core_Action::DISABLE] = [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable Petition'), - ); - self::$_petitionActionLinks[CRM_Core_Action::ENABLE] = array( + ]; + self::$_petitionActionLinks[CRM_Core_Action::ENABLE] = [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable Petition'), - ); - self::$_petitionActionLinks[CRM_Core_Action::DELETE] = array( + ]; + self::$_petitionActionLinks[CRM_Core_Action::DELETE] = [ 'name' => ts('Delete'), 'url' => 'civicrm/petition/add', 'qs' => 'action=delete&id=%%id%%&reset=1', 'title' => ts('Delete Petition'), - ); - self::$_petitionActionLinks[CRM_Core_Action::PROFILE] = array( + ]; + self::$_petitionActionLinks[CRM_Core_Action::PROFILE] = [ 'name' => ts('Sign'), 'url' => 'civicrm/petition/sign', 'qs' => 'sid=%%id%%&reset=1', 'title' => ts('Sign Petition'), 'fe' => TRUE, //CRM_Core_Action::PROFILE is used because there isn't a specific action for sign - ); - self::$_petitionActionLinks[CRM_Core_Action::BROWSE] = array( + ]; + self::$_petitionActionLinks[CRM_Core_Action::BROWSE] = [ 'name' => ts('Signatures'), 'url' => 'civicrm/activity/search', 'qs' => 'survey=%%id%%&force=1', 'title' => ts('List the signatures'), //CRM_Core_Action::PROFILE is used because there isn't a specific action for sign - ); + ]; } return self::$_petitionActionLinks; @@ -196,8 +196,8 @@ public function browseCampaign() { * * @return array */ - public static function getCampaignSummary($params = array()) { - $campaignsData = array(); + public static function getCampaignSummary($params = []) { + $campaignsData = []; //get the campaigns. $campaigns = CRM_Campaign_BAO_Campaign::getCampaignSummary($params); @@ -205,7 +205,7 @@ public static function getCampaignSummary($params = array()) { $config = CRM_Core_Config::singleton(); $campaignType = CRM_Campaign_PseudoConstant::campaignType(); $campaignStatus = CRM_Campaign_PseudoConstant::campaignStatus(); - $properties = array( + $properties = [ 'id', 'name', 'title', @@ -215,7 +215,7 @@ public static function getCampaignSummary($params = array()) { 'is_active', 'start_date', 'end_date', - ); + ]; foreach ($campaigns as $cmpid => $campaign) { foreach ($properties as $prop) { $campaignsData[$cmpid][$prop] = CRM_Utils_Array::value($prop, $campaign); @@ -251,7 +251,7 @@ public static function getCampaignSummary($params = array()) { } $campaignsData[$cmpid]['action'] = CRM_Core_Action::formLink(self::campaignActionLinks(), $action, - array('id' => $campaign['id']), + ['id' => $campaign['id']], ts('more'), FALSE, 'campaign.dashboard.row', @@ -296,8 +296,8 @@ public function browseSurvey() { * * @return array */ - public static function getSurveySummary($params = array()) { - $surveysData = array(); + public static function getSurveySummary($params = []) { + $surveysData = []; //get the survey. $config = CRM_Core_Config::singleton(); @@ -311,7 +311,7 @@ public static function getSurveySummary($params = array()) { $surveysData[$sid]['campaign'] = CRM_Utils_Array::value($campaignId, $campaigns); $surveysData[$sid]['activity_type'] = $surveyType[$survey['activity_type_id']]; if (!empty($survey['release_frequency'])) { - $surveysData[$sid]['release_frequency'] = ts('1 Day', array('plural' => '%count Days', 'count' => $survey['release_frequency'])); + $surveysData[$sid]['release_frequency'] = ts('1 Day', ['plural' => '%count Days', 'count' => $survey['release_frequency']]); } $action = array_sum(array_keys(self::surveyActionLinks($surveysData[$sid]['activity_type']))); @@ -344,7 +344,7 @@ public static function getSurveySummary($params = array()) { } $surveysData[$sid]['action'] = CRM_Core_Action::formLink(self::surveyActionLinks($surveysData[$sid]['activity_type']), $action, - array('id' => $sid), + ['id' => $sid], ts('more'), FALSE, 'survey.dashboard.row', @@ -402,9 +402,9 @@ public function browsePetition() { * * @return array */ - public static function getPetitionSummary($params = array()) { + public static function getPetitionSummary($params = []) { $config = CRM_Core_Config::singleton(); - $petitionsData = array(); + $petitionsData = []; //get the petitions. $petitions = CRM_Campaign_BAO_Petition::getPetitionSummary($params); @@ -439,7 +439,7 @@ public static function getPetitionSummary($params = array()) { $petitionsData[$pid]['action'] = CRM_Core_Action::formLink(self::petitionActionLinks(), $action, - array('id' => $pid), + ['id' => $pid], ts('more'), FALSE, 'petition.dashboard.row', @@ -453,11 +453,11 @@ public static function getPetitionSummary($params = array()) { } public function browse() { - $this->_tabs = array( + $this->_tabs = [ 'campaign' => ts('Campaigns'), 'survey' => ts('Surveys'), 'petition' => ts('Petitions'), - ); + ]; $subPageType = CRM_Utils_Request::retrieve('type', 'String', $this); if ($subPageType) { @@ -474,11 +474,11 @@ public function browse() { } CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') - ->addSetting(array( - 'tabSettings' => array( + ->addSetting([ + 'tabSettings' => [ 'active' => strtolower(CRM_Utils_Array::value('subPage', $_GET, 'campaign')), - ), - )); + ], + ]); } /** @@ -495,14 +495,14 @@ public function run() { } public function buildTabs() { - $allTabs = array(); + $allTabs = []; foreach ($this->_tabs as $name => $title) { - $allTabs[$name] = array( + $allTabs[$name] = [ 'title' => $title, 'valid' => TRUE, 'active' => TRUE, 'link' => CRM_Utils_System::url('civicrm/campaign', "reset=1&type=$name"), - ); + ]; } $allTabs['campaign']['class'] = 'livePage'; $this->assign('tabHeader', $allTabs); diff --git a/CRM/Campaign/Page/Petition.php b/CRM/Campaign/Page/Petition.php index 9a10f16752e9..c526df7f0eee 100644 --- a/CRM/Campaign/Page/Petition.php +++ b/CRM/Campaign/Page/Petition.php @@ -1,9 +1,9 @@ assign('survey_id', $petition_id); $pparams['id'] = $petition_id; - $this->petition = array(); + $this->petition = []; CRM_Campaign_BAO_Survey::retrieve($pparams, $this->petition); $this->assign('is_share', CRM_Utils_Array::value('is_share', $this->petition)); $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $this->petition)); @@ -119,7 +120,7 @@ public static function confirm($contact_id, $subscribe_id, $hash, $activity_id, $ce->save(); CRM_Contact_BAO_GroupContact::addContactsToGroup( - array($contact_id), + [$contact_id], $se->group_id, 'Email', 'Added', diff --git a/CRM/Campaign/Page/Petition/ThankYou.php b/CRM/Campaign/Page/Petition/ThankYou.php index f0fc9fffb21e..ac4d8d4b8342 100644 --- a/CRM/Campaign/Page/Petition/ThankYou.php +++ b/CRM/Campaign/Page/Petition/ThankYou.php @@ -1,9 +1,9 @@ petition = array(); + $this->petition = []; CRM_Campaign_BAO_Survey::retrieve($params, $this->petition); $this->assign('petitionTitle', $this->petition['title']); $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $this->petition)); diff --git a/CRM/Campaign/Page/SurveyType.php b/CRM/Campaign/Page/SurveyType.php index 74da1d9f3186..bfe7e4cd906c 100644 --- a/CRM/Campaign/Page/SurveyType.php +++ b/CRM/Campaign/Page/SurveyType.php @@ -1,9 +1,9 @@ assign('gName', $this->_gName); $this->assign('GName', $this->_GName); - CRM_Utils_System::setTitle(ts('%1 Options', array(1 => $this->_GName))); + CRM_Utils_System::setTitle(ts('%1 Options', [1 => $this->_GName])); - $this->assign('addSurveyType', array("civicrm/admin/campaign/surveyType", "reset=1&action=add")); + $this->assign('addSurveyType', ["civicrm/admin/campaign/surveyType", "reset=1&action=add"]); } /** @@ -102,30 +102,30 @@ public function getBAOName() { */ public function &links() { if (!(self::$_links)) { - self::$_links = array( - CRM_Core_Action::UPDATE => array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/admin/campaign/surveyType', 'qs' => 'action=update&id=%%id%%&reset=1', - 'title' => ts('Edit %1', array(1 => $this->_gName)), - ), - CRM_Core_Action::DISABLE => array( + 'title' => ts('Edit %1', [1 => $this->_gName]), + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', - 'title' => ts('Disable %1', array(1 => $this->_gName)), - ), - CRM_Core_Action::ENABLE => array( + 'title' => ts('Disable %1', [1 => $this->_gName]), + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', - 'title' => ts('Enable %1', array(1 => $this->_gName)), - ), - CRM_Core_Action::DELETE => array( + 'title' => ts('Enable %1', [1 => $this->_gName]), + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/admin/campaign/surveyType', 'qs' => 'action=delete&id=%%id%%', - 'title' => ts('Delete %1 Type', array(1 => $this->_gName)), - ), - ); + 'title' => ts('Delete %1 Type', [1 => $this->_gName]), + ], + ]; } return self::$_links; } @@ -143,7 +143,7 @@ public function run() { */ public function browse() { $campaingCompId = CRM_Core_Component::getComponentID('CiviCampaign'); - $groupParams = array('name' => $this->_gName); + $groupParams = ['name' => $this->_gName]; $optionValues = CRM_Core_OptionValue::getRows($groupParams, $this->links(), 'component_id,weight'); foreach ($optionValues as $key => $optionValue) { diff --git a/CRM/Campaign/Page/Vote.php b/CRM/Campaign/Page/Vote.php index 76586b3699ad..ad131746ff45 100644 --- a/CRM/Campaign/Page/Vote.php +++ b/CRM/Campaign/Page/Vote.php @@ -1,9 +1,9 @@ _tabs = array( + $this->_tabs = [ 'reserve' => ts('Reserve Respondents'), 'interview' => ts('Interview Respondents'), - ); + ]; $this->_surveyId = CRM_Utils_Request::retrieve('sid', 'Positive', $this); $this->_interviewerId = CRM_Utils_Request::retrieve('cid', 'Positive', $this); @@ -93,11 +93,11 @@ public function browse() { CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') - ->addSetting(array( - 'tabSettings' => array( + ->addSetting([ + 'tabSettings' => [ 'active' => strtolower(CRM_Utils_Array::value('subPage', $_GET, 'reserve')), - ), - )); + ], + ]); } /** @@ -110,17 +110,16 @@ public function run() { } public function buildTabs() { - $allTabs = array(); + $allTabs = []; foreach ($this->_tabs as $name => $title) { // check for required permissions. - if (!CRM_Core_Permission::check(array( - array( + if (!CRM_Core_Permission::check([ + [ 'manage campaign', 'administer CiviCampaign', "{$name} campaign contacts", - ), - )) - ) { + ], + ])) { continue; } @@ -131,12 +130,12 @@ public function buildTabs() { if ($this->_interviewerId) { $urlParams .= "&cid={$this->_interviewerId}"; } - $allTabs[$name] = array( + $allTabs[$name] = [ 'title' => $title, 'valid' => TRUE, 'active' => TRUE, 'link' => CRM_Utils_System::url('civicrm/campaign/vote', $urlParams), - ); + ]; } $this->assign('tabHeader', empty($allTabs) ? FALSE : $allTabs); diff --git a/CRM/Campaign/PseudoConstant.php b/CRM/Campaign/PseudoConstant.php index 15030a7a283c..e121a86d5b6f 100644 --- a/CRM/Campaign/PseudoConstant.php +++ b/CRM/Campaign/PseudoConstant.php @@ -1,9 +1,9 @@ fetch()) { $this->_query->convertToPseudoNames($result); - $row = array(); + $row = []; // the columns we are interested in foreach (self::$_properties as $property) { if (property_exists($result, $property)) { @@ -271,30 +271,34 @@ public function buildPrevNextCache($sort) { if (!$crmPID) { $cacheKey = "civicrm search {$this->_key}"; - CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKey, 'civicrm_contact'); + Civi::service('prevnext')->deleteItem(NULL, $cacheKey, 'civicrm_contact'); - $sql = $this->_query->searchQuery(0, 0, $sort, + $sql = $this->_query->getSearchSQLParts(0, 0, $sort, FALSE, FALSE, FALSE, FALSE, - TRUE, $this->_campaignWhereClause, + $this->_campaignWhereClause, NULL, $this->_campaignFromClause ); - list($select, $from) = explode(' FROM ', $sql); - $insertSQL = " -INSERT INTO civicrm_prevnext_cache ( entity_table, entity_id1, entity_id2, cacheKey, data ) -SELECT 'civicrm_contact', contact_a.id, contact_a.id, '$cacheKey', contact_a.display_name -FROM {$from} + + $selectSQL = " + SELECT %1, contact_a.id, contact_a.display_name +FROM {$sql['from']} "; - $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); - $result = CRM_Core_DAO::executeQuery($insertSQL); - unset($errorScope); - if (is_a($result, 'DB_Error')) { + try { + Civi::service('prevnext')->fillWithSql($cacheKey, $selectSQL, [1 => [$cacheKey, 'String']]); + } + catch (CRM_Core_Exception $e) { + // Heavy handed, no? Seems like this merits an explanation. return; } - // also record an entry in the cache key table, so we can delete it periodically - CRM_Core_BAO_Cache::setItem($cacheKey, 'CiviCRM Search PrevNextCache', $cacheKey); + + if (Civi::service('prevnext') instanceof CRM_Core_PrevNextCache_Sql) { + // SQL-backed prevnext cache uses an extra record for pruning the cache. + // Also ensure that caches stay alive for 2 days a per previous code. + Civi::cache('prevNextCache')->set($cacheKey, $cacheKey, 60 * 60 * 24 * CRM_Core_PrevNextCache_Sql::cacheDays); + } } } @@ -319,40 +323,40 @@ public function getQILL() { * the column headers that need to be displayed */ public function &getColumnHeaders($action = NULL, $output = NULL) { - self::$_columnHeaders = array(); + self::$_columnHeaders = []; if (!$this->_single) { - $contactDetails = array( - array( + $contactDetails = [ + [ 'name' => ts('Contact Name'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - array( + ], + [ 'name' => ts('Street Number'), 'sort' => 'street_number', - ), - array( + ], + [ 'name' => ts('Street Name'), 'sort' => 'street_name', - ), - array('name' => ts('Street Address')), - array( + ], + ['name' => ts('Street Address')], + [ 'name' => ts('City'), 'sort' => 'city', - ), - array( + ], + [ 'name' => ts('Postal Code'), 'sort' => 'postal_code', - ), - array( + ], + [ 'name' => ts('State'), 'sort' => 'state_province_name', - ), - array('name' => ts('Country')), - array('name' => ts('Email')), - array('name' => ts('Phone')), - ); + ], + ['name' => ts('Country')], + ['name' => ts('Email')], + ['name' => ts('Phone')], + ]; self::$_columnHeaders = array_merge($contactDetails, self::$_columnHeaders); } diff --git a/CRM/Campaign/StateMachine/Search.php b/CRM/Campaign/StateMachine/Search.php index 4c04c01a7f65..6d38b3aa5f61 100644 --- a/CRM/Campaign/StateMachine/Search.php +++ b/CRM/Campaign/StateMachine/Search.php @@ -1,9 +1,9 @@ _pages = array(); + $this->_pages = []; $this->_pages['CRM_Campaign_Form_Search'] = NULL; list($task, $result) = $this->taskName($controller, 'Search'); @@ -78,7 +78,7 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { * * @param string $formName * - * @return string + * @return array * the name of the form that will handle the task */ public function taskName($controller, $formName = 'Search') { diff --git a/CRM/Campaign/Task.php b/CRM/Campaign/Task.php index 2b12d19e0047..6e4367b0a6f8 100644 --- a/CRM/Campaign/Task.php +++ b/CRM/Campaign/Task.php @@ -1,9 +1,9 @@ array( + self::$_tasks = [ + self::INTERVIEW => [ 'title' => ts('Record Respondents Interview'), - 'class' => array( + 'class' => [ 'CRM_Campaign_Form_Task_Interview', 'CRM_Campaign_Form_Task_Release', - ), + ], 'result' => FALSE, - ), - 2 => array( + ], + self::RESERVE => [ 'title' => ts('Reserve Respondents'), - 'class' => array( + 'class' => [ 'CRM_Campaign_Form_Task_Reserve', 'CRM_Campaign_Form_Task_Interview', 'CRM_Campaign_Form_Task_Release', - ), + ], 'result' => FALSE, - ), - 3 => array( + ], + self::RELEASE => [ 'title' => ts('Release Respondents'), 'class' => 'CRM_Campaign_Form_Task_Release', 'result' => FALSE, - ), - 4 => array( + ], + self::TASK_PRINT => [ 'title' => ts('Print Respondents'), 'class' => 'CRM_Campaign_Form_Task_Print', 'result' => FALSE, - ), - ); + ], + ]; - CRM_Utils_Hook::searchTasks('campaign', self::$_tasks); - asort(self::$_tasks); + parent::tasks(); } return self::$_tasks; } - /** - * These tasks are the core set of task titles - * on voters. - * - * @return array - * the set of task titles - */ - public static function &taskTitles() { - self::tasks(); - $titles = array(); - foreach (self::$_tasks as $id => $value) { - $titles[$id] = $value['title']; - } - - return $titles; - } - /** * Show tasks selectively based on the permission level * of the user * * @param int $permission + * @param array $params * * @return array * set of tasks that are valid for the user */ - public static function &permissionedTaskTitles($permission) { + public static function permissionedTaskTitles($permission, $params = []) { $tasks = self::taskTitles(); + $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params); return $tasks; } @@ -143,14 +125,14 @@ public static function &permissionedTaskTitles($permission) { public static function getTask($value) { self::tasks(); if (!$value || !CRM_Utils_Array::value($value, self::$_tasks)) { - // make the interview task by default - $value = 1; + // Set the interview task as default + $value = self::INTERVIEW; } - return array( + return [ self::$_tasks[$value]['class'], self::$_tasks[$value]['result'], - ); + ]; } } diff --git a/CRM/Campaign/xml/Menu/Campaign.xml b/CRM/Campaign/xml/Menu/Campaign.xml index a5c673f24eb8..555eb1f2843b 100644 --- a/CRM/Campaign/xml/Menu/Campaign.xml +++ b/CRM/Campaign/xml/Menu/Campaign.xml @@ -72,7 +72,7 @@ civicrm/admin/setting/preferences/campaign CiviCampaign Component Settings - CRM_Admin_Form_Preferences_Campaign + CRM_Admin_Form_Generic Configure global CiviCampaign behaviors. CiviCampaign 10 diff --git a/CRM/Case/Audit/Audit.php b/CRM/Case/Audit/Audit.php index 1928fa4e4383..f58660fef31b 100644 --- a/CRM/Case/Audit/Audit.php +++ b/CRM/Case/Audit/Audit.php @@ -22,7 +22,7 @@ public function __construct($xmlString, $confFilename) { * @return array */ public function getActivities($printReport = FALSE) { - $retval = array(); + $retval = []; /* * Loop through the activities in the file and add them to the appropriate region array. @@ -41,16 +41,16 @@ public function getActivities($printReport = FALSE) { $activityindex = 0; $activityList = $doc->getElementsByTagName("Activity"); - $caseActivities = array(); - $activityStatusType = array(); + $caseActivities = []; + $activityStatusType = []; foreach ($activityList as $activity) { - $retval[$activityindex] = array(); + $retval[$activityindex] = []; - $ifBlankReplacements = array(); + $ifBlankReplacements = []; $completed = FALSE; - $sortValues = array('1970-01-01'); + $sortValues = ['1970-01-01']; $category = ''; $fieldindex = 1; $fields = $activity->getElementsByTagName("Field"); @@ -88,7 +88,7 @@ public function getActivities($printReport = FALSE) { } if ($this->auditConfig->includeInRegion($label, $region)) { - $retval[$activityindex][$region][$fieldindex] = array(); + $retval[$activityindex][$region][$fieldindex] = []; $retval[$activityindex][$region][$fieldindex]['label'] = $label; $retval[$activityindex][$region][$fieldindex]['datatype'] = $datatype; $retval[$activityindex][$region][$fieldindex]['value'] = $value; @@ -98,18 +98,18 @@ public function getActivities($printReport = FALSE) { //CRM-4570 if ($printReport) { - if (!in_array($label, array( + if (!in_array($label, [ 'Activity Type', 'Status', - )) + ]) ) { - $caseActivities[$activityindex][$fieldindex] = array(); + $caseActivities[$activityindex][$fieldindex] = []; $caseActivities[$activityindex][$fieldindex]['label'] = $label; $caseActivities[$activityindex][$fieldindex]['datatype'] = $datatype; $caseActivities[$activityindex][$fieldindex]['value'] = $value; } else { - $activityStatusType[$activityindex][$fieldindex] = array(); + $activityStatusType[$activityindex][$fieldindex] = []; $activityStatusType[$activityindex][$fieldindex]['label'] = $label; $activityStatusType[$activityindex][$fieldindex]['datatype'] = $datatype; $activityStatusType[$activityindex][$fieldindex]['value'] = $value; @@ -166,10 +166,10 @@ public function getActivities($printReport = FALSE) { } if ($printReport) { - @uasort($caseActivities, array($this, "compareActivities")); + @uasort($caseActivities, [$this, "compareActivities"]); } else { - @uasort($retval, array($this, "compareActivities")); + @uasort($retval, [$this, "compareActivities"]); } } diff --git a/CRM/Case/Audit/AuditConfig.php b/CRM/Case/Audit/AuditConfig.php index 5065cca44b79..be1ee8a08d47 100644 --- a/CRM/Case/Audit/AuditConfig.php +++ b/CRM/Case/Audit/AuditConfig.php @@ -22,8 +22,8 @@ public function __construct($filename) { // set some defaults $this->completionLabel = "Status"; $this->completionValue = "Completed"; - $this->sortByLabels = array("Actual Date", "Due Date"); - $this->ifBlanks = array(); + $this->sortByLabels = ["Actual Date", "Due Date"]; + $this->ifBlanks = []; $this->loadConfig(); } @@ -57,8 +57,8 @@ public function getIfBlanks() { } public function loadConfig() { - $this->regionFieldList = array(); - $this->includeRules = array(); + $this->regionFieldList = []; + $this->includeRules = []; $doc = new DOMDocument(); $xmlString = file_get_contents(dirname(__FILE__) . '/' . $this->filename); @@ -67,14 +67,14 @@ public function loadConfig() { $regions = $doc->getElementsByTagName("region"); foreach ($regions as $region) { $regionName = $region->getAttribute("name"); - $this->regionFieldList[$regionName] = array(); + $this->regionFieldList[$regionName] = []; // Inclusion/exclusion settings $includeRule = $region->getAttribute("includeRule"); if (empty($includeRule)) { $includeRule = 'include'; } - $this->includeRules[$regionName] = array('rule' => $includeRule); + $this->includeRules[$regionName] = ['rule' => $includeRule]; if ($includeRule == 'exclude') { $altRegion = $region->getAttribute("exclusionCorrespondingRegion"); $this->includeRules[$regionName]['altRegion'] = $altRegion; @@ -124,7 +124,7 @@ public function loadConfig() { $sortElement = $doc->getElementsByTagName("sortByLabels"); if (!empty($sortElement)) { - $this->sortByLabels = array(); + $this->sortByLabels = []; $label_elements = $sortElement->item(0)->getElementsByTagName("label"); foreach ($label_elements as $ele) { $this->sortByLabels[] = $ele->nodeValue; diff --git a/CRM/Case/BAO/Case.php b/CRM/Case/BAO/Case.php index e8b15c89657f..f947784c068b 100644 --- a/CRM/Case/BAO/Case.php +++ b/CRM/Case/BAO/Case.php @@ -1,9 +1,9 @@ copyValues($params); - return $caseDAO->save(); + $result = $caseDAO->save(); + // Get other case values (required by XML processor), this adds to $result array + $caseDAO->find(TRUE); + return $result; } /** @@ -87,6 +90,15 @@ public static function add(&$params) { * @return CRM_Case_BAO_Case */ public static function &create(&$params) { + // CRM-20958 - These fields are managed by MySQL triggers. Watch out for clients resaving stale timestamps. + unset($params['created_date']); + unset($params['modified_date']); + $caseStatus = CRM_Case_PseudoConstant::caseStatus('name'); + // for resolved case the end date should set to now + if (!empty($params['status_id']) && $params['status_id'] == array_search('Closed', $caseStatus)) { + $params['end_date'] = date("Ymd"); + } + $transaction = new CRM_Core_Transaction(); if (!empty($params['id'])) { @@ -263,15 +275,16 @@ public static function enableDisableCaseRelationships($caseId, $enable) { * ID of the case. * * @param int $contactID + * @param int $startArrayAt This is to support legacy calls to Case.Get API which may rely on the first array index being set to 1 * * @return array */ - public static function retrieveContactIdsByCaseId($caseId, $contactID = NULL) { + public static function retrieveContactIdsByCaseId($caseId, $contactID = NULL, $startArrayAt = 0) { $caseContact = new CRM_Case_DAO_CaseContact(); $caseContact->case_id = $caseId; $caseContact->find(); $contactArray = array(); - $count = 1; + $count = $startArrayAt; while ($caseContact->fetch()) { if ($contactID != $caseContact->contact_id) { $contactArray[$count] = $caseContact->contact_id; @@ -329,7 +342,7 @@ public static function getContactNames($caseId) { LEFT JOIN civicrm_case_contact ON civicrm_case_contact.contact_id = contact_a.id LEFT JOIN civicrm_email ce ON ( ce.contact_id = contact_a.id AND ce.is_primary = 1) LEFT JOIN civicrm_phone cp ON ( cp.contact_id = contact_a.id AND cp.is_primary = 1) - WHERE civicrm_case_contact.case_id = %1 + WHERE contact_a.is_deleted = 0 AND civicrm_case_contact.case_id = %1 ORDER BY civicrm_case_contact.id"; $dao = CRM_Core_DAO::executeQuery($query, @@ -384,7 +397,6 @@ public static function retrieveCaseIdsByContactId($contactID, $includeDeleted = $caseArray[] = $dao->id; } - $dao->free(); return $caseArray; } @@ -395,145 +407,93 @@ public static function retrieveCaseIdsByContactId($contactID, $includeDeleted = * * @return string */ - public static function getCaseActivityQuery($type = 'upcoming', $userID = NULL, $condition = NULL) { - if (!$userID) { - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); - } - - $query = "SELECT -civicrm_case.id as case_id, -civicrm_case.subject as case_subject, -civicrm_contact.id as contact_id, -civicrm_contact.sort_name as sort_name, -civicrm_phone.phone as phone, -civicrm_contact.contact_type as contact_type, -civicrm_contact.contact_sub_type as contact_sub_type, -t_act.activity_type_id, -c_type.title as case_type, -civicrm_case.case_type_id as case_type_id, -cov_status.label as case_status, -cov_status.label as case_status_name, -t_act.status_id, -civicrm_case.start_date as case_start_date, -case_relation_type.label_b_a as case_role, "; - - if ($type == 'upcoming') { - $query .= " -t_act.desired_date as case_scheduled_activity_date, -t_act.id as case_scheduled_activity_id, -t_act.act_type_name as case_scheduled_activity_type_name, -t_act.act_type AS case_scheduled_activity_type "; - } - elseif ($type == 'recent') { - $query .= " -t_act.desired_date as case_recent_activity_date, -t_act.id as case_recent_activity_id, -t_act.act_type_name as case_recent_activity_type_name, -t_act.act_type AS case_recent_activity_type "; - } - elseif ($type == 'any') { - $query .= " -t_act.desired_date as case_activity_date, -t_act.id as case_activity_id, -t_act.act_type_name as case_activity_type_name, -t_act.act_type AS case_activity_type "; - } - - $query .= " FROM civicrm_case - INNER JOIN civicrm_case_contact ON civicrm_case.id = civicrm_case_contact.case_id - INNER JOIN civicrm_contact ON civicrm_case_contact.contact_id = civicrm_contact.id "; - - if ($type == 'upcoming') { - // This gets the earliest activity per case that's scheduled within 14 days from now. - // Note we have an inner select to get the min activity id in order to remove duplicates in case there are two with the same datetime. - // In this case we don't really care which one, so min(id) works. - // optimized in CRM-11837 - $query .= " INNER JOIN -( - SELECT case_id, act.id, activity_date_time AS desired_date, activity_type_id, status_id, aov.name AS act_type_name, aov.label AS act_type - FROM ( - SELECT * - FROM ( - SELECT * - FROM civicrm_view_case_activity_upcoming - ORDER BY activity_date_time ASC, id ASC - ) AS upcomingOrdered - ) AS act - LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov ON ( aov.option_group_id = aog.id AND aov.value = act.activity_type_id ) -) AS t_act -"; - } - elseif ($type == 'recent') { - // Similarly, the most recent activity in the past 14 days, and exclude scheduled. - //improve query performance - CRM-10598 - $query .= " INNER JOIN -( - SELECT case_id, act.id, activity_date_time AS desired_date, activity_type_id, status_id, aov.name AS act_type_name, aov.label AS act_type - FROM ( - SELECT * - FROM ( - SELECT * - FROM civicrm_view_case_activity_recent - ORDER BY activity_date_time DESC, id ASC - ) AS recentOrdered - ) AS act -LEFT JOIN civicrm_option_group aog ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov ON ( aov.option_group_id = aog.id AND aov.value = act.activity_type_id ) -) AS t_act "; - } - elseif ($type == 'any') { - $query .= " LEFT JOIN -( - SELECT ca4.case_id, act4.id AS id, act4.activity_date_time AS desired_date, act4.activity_type_id, act4.status_id, aov.name AS act_type_name, aov.label AS act_type - FROM civicrm_activity act4 - LEFT JOIN civicrm_case_activity ca4 - ON ca4.activity_id = act4.id - AND act4.is_current_revision = 1 - LEFT JOIN civicrm_option_group aog - ON aog.name='activity_type' - LEFT JOIN civicrm_option_value aov - ON aov.option_group_id = aog.id - AND aov.value = act4.activity_type_id -) AS t_act"; - } - - $query .= " - ON t_act.case_id = civicrm_case.id - LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary=1) - LEFT JOIN civicrm_relationship case_relationship - ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} - AND case_relationship.case_id = civicrm_case.id ) - - LEFT JOIN civicrm_relationship_type case_relation_type - ON ( case_relation_type.id = case_relationship.relationship_type_id - AND case_relation_type.id = case_relationship.relationship_type_id ) - - LEFT JOIN civicrm_case_type c_type - ON civicrm_case.case_type_id = c_type.id - - LEFT JOIN civicrm_option_group cog_status - ON cog_status.name = 'case_status' - - LEFT JOIN civicrm_option_value cov_status - ON ( civicrm_case.status_id = cov_status.value - AND cog_status.id = cov_status.option_group_id ) -"; + public static function getCaseActivityCountQuery($type = 'upcoming', $userID, $condition = NULL) { + return sprintf(" SELECT COUNT(*) FROM (%s) temp ", self::getCaseActivityQuery($type, $userID, $condition)); + } + + /** + * @param string $type + * @param int $userID + * @param string $condition + * @param string $limit + * @param string $order + * + * @return string + */ + public static function getCaseActivityQuery($type = 'upcoming', $userID, $condition = NULL, $limit = NULL, $order = NULL) { + $selectClauses = array( + 'civicrm_case.id as case_id', + 'civicrm_case.subject as case_subject', + 'civicrm_contact.id as contact_id', + 'civicrm_contact.sort_name as sort_name', + 'civicrm_phone.phone as phone', + 'civicrm_contact.contact_type as contact_type', + 'civicrm_contact.contact_sub_type as contact_sub_type', + 't_act.activity_type_id as activity_type_id', + 'civicrm_case.case_type_id as case_type_id', + 'civicrm_case.status_id as case_status_id', + 't_act.status_id as status_id', + 'civicrm_case.start_date as case_start_date', + "GROUP_CONCAT(DISTINCT IF(case_relationship.contact_id_b = $userID, case_relation_type.label_a_b, case_relation_type.label_b_a) SEPARATOR ', ') as case_role", + 't_act.activity_date_time as activity_date_time', + 't_act.id as activity_id', + ); + + $query = CRM_Contact_BAO_Query::appendAnyValueToSelect($selectClauses, 'case_id'); + + $query .= <<get('userID'); + // Return cached value instead of re-running query + if (isset(Civi::$statics[__CLASS__]['totalCount']) && $getCount) { + return Civi::$statics[__CLASS__]['totalCount']; } - //validate access for all cases. + $type = CRM_Utils_Array::value('type', $params, 'upcoming'); + $userID = CRM_Core_Session::singleton()->get('userID'); + + // validate access for all cases. if ($allCases && !CRM_Core_Permission::check('access all cases and activities')) { $allCases = FALSE; } - $condition = " AND civicrm_case.is_deleted = 0 AND civicrm_contact.is_deleted <> 1"; + $whereClauses = array('civicrm_case.is_deleted = 0 AND civicrm_contact.is_deleted <> 1'); if (!$allCases) { - $condition .= " AND case_relationship.contact_id_b = {$userID} "; + $whereClauses[] = "(case_relationship.contact_id_b = {$userID} OR case_relationship.contact_id_a = {$userID})"; + $whereClauses[] = 'case_relationship.is_active'; } - if ($type == 'upcoming' || $type == 'any') { - $closedId = CRM_Core_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Closed'); - $condition .= " -AND civicrm_case.status_id != $closedId"; + if (empty($params['status_id']) && ($type == 'upcoming' || $type == 'any')) { + $whereClauses[] = "civicrm_case.status_id != " . CRM_Core_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Closed'); } - $query = self::getCaseActivityQuery($type, $userID, $condition); - - $queryParams = array(); - $result = CRM_Core_DAO::executeQuery($query, - $queryParams - ); - - $caseStatus = CRM_Core_OptionGroup::values('case_status', FALSE, FALSE, FALSE, " AND v.name = 'Urgent' "); - - $resultFields = array( - 'contact_id', - 'contact_type', - 'sort_name', - 'phone', - 'case_id', - 'case_subject', - 'case_type', - 'case_type_id', - 'status_id', - 'case_status', - 'case_status_name', - 'activity_type_id', - 'case_start_date', - 'case_role', - ); + foreach (array('case_type_id', 'status_id') as $column) { + if (!empty($params[$column])) { + $whereClauses[] = sprintf("civicrm_case.%s IN (%s)", $column, $params[$column]); + } + } + $condition = implode(' AND ', $whereClauses); - if ($type == 'upcoming') { - $resultFields[] = 'case_scheduled_activity_date'; - $resultFields[] = 'case_scheduled_activity_type_name'; - $resultFields[] = 'case_scheduled_activity_type'; - $resultFields[] = 'case_scheduled_activity_id'; + Civi::$statics[__CLASS__]['totalCount'] = $totalCount = CRM_Core_DAO::singleValueQuery(self::getCaseActivityCountQuery($type, $userID, $condition)); + if ($getCount) { + return $totalCount; } - elseif ($type == 'recent') { - $resultFields[] = 'case_recent_activity_date'; - $resultFields[] = 'case_recent_activity_type_name'; - $resultFields[] = 'case_recent_activity_type'; - $resultFields[] = 'case_recent_activity_id'; + $casesList['total'] = $totalCount; + + $limit = ''; + if (!empty($params['rp'])) { + $params['offset'] = ($params['page'] - 1) * $params['rp']; + $params['rowCount'] = $params['rp']; + if (!empty($params['rowCount']) && $params['rowCount'] > 0) { + $limit = " LIMIT {$params['offset']}, {$params['rowCount']} "; + } } - elseif ($type == 'any') { - $resultFields[] = 'case_activity_date'; - $resultFields[] = 'case_activity_type_name'; - $resultFields[] = 'case_activity_type'; - $resultFields[] = 'case_activity_id'; + + $order = NULL; + if (!empty($params['sortBy'])) { + if (strstr($params['sortBy'], 'date ')) { + $params['sortBy'] = str_replace('date', 'activity_date_time', $params['sortBy']); + } + $order = "ORDER BY " . $params['sortBy']; } + $query = self::getCaseActivityQuery($type, $userID, $condition, $limit, $order); + $result = CRM_Core_DAO::executeQuery($query); + // we're going to use the usual actions, so doesn't make sense to duplicate definitions $actions = CRM_Case_Selector_Search::links(); @@ -644,55 +590,81 @@ public static function getCases($allCases = TRUE, $userID = NULL, $type = 'upcom } $mask = CRM_Core_Action::mask($permissions); - while ($result->fetch()) { - foreach ($resultFields as $donCare => $field) { - $casesList[$result->case_id][$field] = $result->$field; - if ($field == 'contact_type') { - $casesList[$result->case_id]['contact_type_icon'] = CRM_Contact_BAO_Contact_Utils::getImage($result->contact_sub_type ? $result->contact_sub_type : $result->contact_type - ); - $casesList[$result->case_id]['action'] = CRM_Core_Action::formLink($actions['primaryActions'], $mask, - array( - 'id' => $result->case_id, - 'cid' => $result->contact_id, - 'cxt' => $context, - ), - ts('more'), - FALSE, - 'case.actions.primary', - 'Case', - $result->case_id - ); - } - elseif ($field == 'case_status') { - if (in_array($result->$field, $caseStatus)) { - $casesList[$result->case_id]['class'] = "status-urgent"; + // Pseudoconstants to populate labels + $caseStatuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE); + $caseTypes = CRM_Case_PseudoConstant::caseType('name'); + $caseTypeTitles = CRM_Case_PseudoConstant::caseType('title', FALSE); + $activityTypeLabels = CRM_Activity_BAO_Activity::buildOptions('activity_type_id'); + + foreach ($result->fetchAll() as $case) { + $key = $case['case_id']; + $casesList[$key] = array(); + $casesList[$key]['DT_RowId'] = $case['case_id']; + $casesList[$key]['DT_RowAttr'] = array('data-entity' => 'case', 'data-id' => $case['case_id']); + $casesList[$key]['DT_RowClass'] = "crm-entity"; + + $casesList[$key]['activity_list'] = sprintf('', + ts('Activities'), + CRM_Utils_System::url('civicrm/case/details', array('caseId' => $case['case_id'], 'cid' => $case['contact_id'], 'type' => $type)) + ); + + $phone = empty($case['phone']) ? '' : '
    ' . $case['phone'] . ''; + $casesList[$key]['contact_id'] = sprintf('%s%s
    %s: %d', + CRM_Utils_System::url('civicrm/contact/view', array('cid' => $case['contact_id'])), + $case['sort_name'], + $phone, + ts('Case ID'), + $case['case_id'] + ); + $casesList[$key]['subject'] = $case['case_subject']; + $casesList[$key]['case_status'] = CRM_Utils_Array::value($case['case_status_id'], $caseStatuses); + if ($case['case_status_id'] == CRM_Case_PseudoConstant::getKey('CRM_Case_BAO_Case', 'case_status_id', 'Urgent')) { + $casesList[$key]['case_status'] = sprintf('%s', strtoupper($casesList[$key]['case_status'])); + } + $casesList[$key]['case_type'] = CRM_Utils_Array::value($case['case_type_id'], $caseTypeTitles); + $casesList[$key]['case_role'] = CRM_Utils_Array::value('case_role', $case, '---'); + $casesList[$key]['manager'] = self::getCaseManagerContact($caseTypes[$case['case_type_id']], $case['case_id']); + + $casesList[$key]['date'] = CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels); + if ($actId = CRM_Utils_Array::value('activity_id', $case)) { + if (self::checkPermission($actId, 'view', $case['activity_type_id'], $userID)) { + if ($type == 'recent') { + $casesList[$key]['date'] = sprintf('%s', + CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id'])), + ts('View activity'), + CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels) + ); } else { - $casesList[$result->case_id]['class'] = "status-normal"; + $status = CRM_Utils_Date::overdue($case['activity_date_time']) ? 'status-overdue' : 'status-scheduled'; + $casesList[$key]['date'] = sprintf('%s   ', + $status, + CRM_Utils_System::url('civicrm/case/activity/view', array('reset' => 1, 'cid' => $case['contact_id'], 'aid' => $case['activity_id'])), + ts('View activity'), + CRM_Utils_Array::value($case['activity_type_id'], $activityTypeLabels) + ); } } + if (isset($case['activity_type_id']) && self::checkPermission($actId, 'edit', $case['activity_type_id'], $userID)) { + $casesList[$key]['date'] .= sprintf('', + CRM_Utils_System::url('civicrm/case/activity', array('reset' => 1, 'cid' => $case['contact_id'], 'caseid' => $case['case_id'], 'action' => 'update', 'id' => $actId)), + ts('Edit activity') + ); + } } - //CRM-4510. - $caseTypes = CRM_Case_PseudoConstant::caseType('name'); - $caseManagerContact = self::getCaseManagerContact($caseTypes[$result->case_type_id], $result->case_id); - if (!empty($caseManagerContact)) { - $casesList[$result->case_id]['casemanager_id'] = CRM_Utils_Array::value('casemanager_id', $caseManagerContact); - $casesList[$result->case_id]['casemanager'] = CRM_Utils_Array::value('casemanager', $caseManagerContact); - } - - //do check user permissions for edit/view activity. - if (($actId = CRM_Utils_Array::value('case_scheduled_activity_id', $casesList[$result->case_id])) || - ($actId = CRM_Utils_Array::value('case_recent_activity_id', $casesList[$result->case_id])) - ) { - $casesList[$result->case_id]["case_{$type}_activity_editable"] = self::checkPermission($actId, - 'edit', - $casesList[$result->case_id]['activity_type_id'], $userID - ); - $casesList[$result->case_id]["case_{$type}_activity_viewable"] = self::checkPermission($actId, - 'view', - $casesList[$result->case_id]['activity_type_id'], $userID - ); - } + $casesList[$key]['date'] .= "
    " . CRM_Utils_Date::customFormat($case['activity_date_time']); + $casesList[$key]['links'] = CRM_Core_Action::formLink($actions['primaryActions'], $mask, + array( + 'id' => $case['case_id'], + 'cid' => $case['contact_id'], + 'cxt' => $context, + ), + ts('more'), + FALSE, + 'case.actions.primary', + 'Case', + $case['case_id'] + ); } return $casesList; @@ -702,10 +674,9 @@ public static function getCases($allCases = TRUE, $userID = NULL, $type = 'upcom * Get the summary of cases counts by type and status. * * @param bool $allCases - * @param int $userID * @return array */ - public static function getCasesSummary($allCases = TRUE, $userID) { + public static function getCasesSummary($allCases = TRUE) { $caseSummary = array(); //validate access for civicase. @@ -713,6 +684,8 @@ public static function getCasesSummary($allCases = TRUE, $userID) { return $caseSummary; } + $userID = CRM_Core_Session::singleton()->get('userID'); + //validate access for all cases. if ($allCases && !CRM_Core_Permission::check('access all cases and activities')) { $allCases = FALSE; @@ -731,26 +704,41 @@ public static function getCasesSummary($allCases = TRUE, $userID) { // build rows with actual data $rows = array(); - $myGroupByClause = $mySelectClause = $myCaseFromClause = $myCaseWhereClause = ''; + $myGroupByClause = $mySelectClause = $myCaseFromClause = $myCaseWhereClauseA = $myCaseWhereClauseB = ''; if ($allCases) { $userID = 'null'; $all = 1; $case_owner = 1; - $myGroupByClause = ' GROUP BY civicrm_case.id'; + $myGroupByClauseB = ' GROUP BY civicrm_case.id'; } else { $all = 0; $case_owner = 2; - $myCaseWhereClause = " AND case_relationship.contact_id_b = {$userID}"; - $myGroupByClause = " GROUP BY CONCAT(case_relationship.case_id,'-',case_relationship.contact_id_b)"; + $myCaseWhereClauseA = " AND case_relationship.contact_id_a = {$userID} AND case_relationship.is_active "; + $myGroupByClauseA = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_a)"; + $myCaseWhereClauseB = " AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active "; + $myGroupByClauseB = " GROUP BY CONCAT(civicrm_case.id,'-',case_relationship.contact_id_b)"; } - $myGroupByClause .= ", case_status.label, status_id, case_type_id"; - + $myGroupByClauseB .= ", case_status.label, status_id, case_type_id, civicrm_case.id"; + $myGroupByClauseA = $myGroupByClauseB; // FIXME: This query could be a lot more efficient if it used COUNT() instead of returning all rows and then counting them with php $query = " -SELECT case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type, - case_type_id, case_relationship.contact_id_b +SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type, + case_type_id, case_relationship.contact_id_b as case_contact + FROM civicrm_case + INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id + LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id + LEFT JOIN civicrm_option_group option_group_case_status ON ( option_group_case_status.name = 'case_status' ) + LEFT JOIN civicrm_option_value case_status ON ( civicrm_case.status_id = case_status.value + AND option_group_case_status.id = case_status.option_group_id ) + LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id = civicrm_case.id + AND case_relationship.contact_id_b = {$userID} AND case_relationship.is_active ) + WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1) +{$myCaseWhereClauseB} {$myGroupByClauseB} +UNION +SELECT civicrm_case.id, case_status.label AS case_status, status_id, civicrm_case_type.title AS case_type, + case_type_id, case_relationship.contact_id_a as case_contact FROM civicrm_case INNER JOIN civicrm_case_contact cc on cc.case_id = civicrm_case.id LEFT JOIN civicrm_case_type ON civicrm_case.case_type_id = civicrm_case_type.id @@ -758,9 +746,9 @@ public static function getCasesSummary($allCases = TRUE, $userID) { LEFT JOIN civicrm_option_value case_status ON ( civicrm_case.status_id = case_status.value AND option_group_case_status.id = case_status.option_group_id ) LEFT JOIN civicrm_relationship case_relationship ON ( case_relationship.case_id = civicrm_case.id - AND case_relationship.contact_id_b = {$userID}) + AND case_relationship.contact_id_a = {$userID}) WHERE is_deleted = 0 AND cc.contact_id IN (SELECT id FROM civicrm_contact WHERE is_deleted <> 1) -{$myCaseWhereClause} {$myGroupByClause}"; +{$myCaseWhereClauseA} {$myGroupByClauseA}"; $res = CRM_Core_DAO::executeQuery($query); while ($res->fetch()) { @@ -789,12 +777,13 @@ public static function getCasesSummary($allCases = TRUE, $userID) { * @param int $caseID * Case id. * @param int $relationshipID + * @param bool $activeOnly * * @return array * case role / relationships * */ - public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) { + public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL, $activeOnly = TRUE) { $query = ' SELECT rel.id as civicrm_relationship_id, con.sort_name as sort_name, @@ -806,11 +795,15 @@ public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) IF(rel.contact_id_a = %1, "a_b", "b_a") as relationship_direction FROM civicrm_relationship rel INNER JOIN civicrm_relationship_type ON rel.relationship_type_id = civicrm_relationship_type.id - INNER JOIN civicrm_contact con ON ((con.id <> %1 AND con.id IN (rel.contact_id_a, rel.contact_id_b)) OR (con.id = %1 AND rel.contact_id_b = rel.contact_id_a AND rel.contact_id_a = %1)) + INNER JOIN civicrm_contact con ON ((con.id <> %1 AND con.id IN (rel.contact_id_a, rel.contact_id_b)) OR (con.id = %1 AND rel.contact_id_b = rel.contact_id_a AND rel.contact_id_a = %1 AND rel.is_active)) LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = con.id AND civicrm_phone.is_primary = 1) LEFT JOIN civicrm_email ON (civicrm_email.contact_id = con.id AND civicrm_email.is_primary = 1) WHERE (rel.contact_id_a = %1 OR rel.contact_id_b = %1) AND rel.case_id = %2 - AND rel.is_active = 1 AND (rel.end_date IS NULL OR rel.end_date > NOW())'; + AND con.is_deleted = 0'; + + if ($activeOnly) { + $query .= ' AND rel.is_active = 1 AND (rel.end_date IS NULL OR rel.end_date > NOW())'; + } $params = array( 1 => array($contactID, 'Positive'), @@ -818,7 +811,7 @@ public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) ); if ($relationshipID) { - $query .= ' AND civicrm_relationship.id = %3 '; + $query .= ' AND rel.id = %3 '; $params[3] = array($relationshipID, 'Integer'); } $dao = CRM_Core_DAO::executeQuery($query, $params); @@ -837,7 +830,6 @@ public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) $values[$rid]['relationship_direction'] = $dao->relationship_direction; } - $dao->free(); return $values; } @@ -853,16 +845,14 @@ public static function getCaseRoles($contactID, $caseID, $relationshipID = NULL) * * @param null $context * @param int $userID - * @param null $type + * @param null $type (deprecated) * * @return array * Array of case activities * */ public static function getCaseActivity($caseID, &$params, $contactID, $context = NULL, $userID = NULL, $type = NULL) { - $values = array(); - - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -1005,189 +995,139 @@ public static function getCaseActivity($caseID, &$params, $contactID, $context = $caseCount = CRM_Core_DAO::singleValueQuery('SELECT FOUND_ROWS()'); $activityTypes = CRM_Case_PseudoConstant::caseActivityType(FALSE, TRUE); - $activityStatuses = CRM_Core_PseudoConstant::activityStatus(); - $url = CRM_Utils_System::url("civicrm/case/activity", - "reset=1&cid={$contactID}&caseid={$caseID}", FALSE, NULL, FALSE - ); - - $contextUrl = ''; - if ($context == 'fulltext') { - $contextUrl = "&context={$context}"; - } - $editUrl = "{$url}&action=update{$contextUrl}"; - $deleteUrl = "{$url}&action=delete{$contextUrl}"; - $restoreUrl = "{$url}&action=renew{$contextUrl}"; - $viewTitle = ts('View activity'); - - $emailActivityTypeIDs = array( - 'Email' => CRM_Core_OptionGroup::getValue('activity_type', - 'Email', - 'name' - ), - 'Inbound Email' => CRM_Core_OptionGroup::getValue('activity_type', - 'Inbound Email', - 'name' - ), + $compStatusValues = array_keys( + CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::COMPLETED) + + CRM_Activity_BAO_Activity::getStatusesByType(CRM_Activity_BAO_Activity::CANCELLED) ); - $caseDeleted = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'is_deleted'); - - // define statuses which are handled like Completed status (others are assumed to be handled like Scheduled status) - $compStatusValues = array(); - $compStatusNames = array('Completed', 'Left Message', 'Cancelled', 'Unreachable', 'Not Required'); - foreach ($compStatusNames as $name) { - $compStatusValues[] = CRM_Core_OptionGroup::getValue('activity_status', $name, 'name'); - } - - $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid=", FALSE, NULL, FALSE); - $hasViewContact = CRM_Core_Permission::giveMeAllACLs(); - $clientIds = self::retrieveContactIdsByCaseId($caseID); - if (!$userID) { - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); + $userID = CRM_Core_Session::getLoggedInContactID(); } - $caseActivities = array(); + $caseActivities = []; while ($dao->fetch()) { - $caseActivity = array(); $caseActivityId = $dao->id; - $allowView = self::checkPermission($caseActivityId, 'view', $dao->activity_type_id, $userID); - $allowEdit = self::checkPermission($caseActivityId, 'edit', $dao->activity_type_id, $userID); - $allowDelete = self::checkPermission($caseActivityId, 'delete', $dao->activity_type_id, $userID); - - //do not have sufficient permission - //to access given case activity record. - if (!$allowView && !$allowEdit && !$allowDelete) { + //Do we have permission to access given case activity record. + if (!self::checkPermission($caseActivityId, 'view', $dao->activity_type_id, $userID)) { continue; } - $caseActivity['DT_RowId'] = $caseActivityId; + $caseActivities[$caseActivityId]['DT_RowId'] = $caseActivityId; //Add classes to the row, via DataTables syntax - $caseActivity['DT_RowClass'] = "crm-entity status-id-$dao->status"; + $caseActivities[$caseActivityId]['DT_RowClass'] = "crm-entity status-id-$dao->status"; if (CRM_Utils_Array::crmInArray($dao->status, $compStatusValues)) { - $caseActivity['DT_RowClass'] .= " status-completed"; + $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-completed"; } else { if (CRM_Utils_Date::overdue($dao->display_date)) { - $caseActivity['DT_RowClass'] .= " status-overdue"; + $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-overdue"; } else { - $caseActivity['DT_RowClass'] .= " status-scheduled"; + $caseActivities[$caseActivityId]['DT_RowClass'] .= " status-scheduled"; } } if (!empty($dao->priority)) { - if ($dao->priority == CRM_Core_OptionGroup::getValue('priority', 'Urgent', 'name')) { - $caseActivity['DT_RowClass'] .= " priority-urgent "; + if ($dao->priority == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Urgent')) { + $caseActivities[$caseActivityId]['DT_RowClass'] .= " priority-urgent "; } - elseif ($dao->priority == CRM_Core_OptionGroup::getValue('priority', 'Low', 'name')) { - $caseActivity['DT_RowClass'] .= " priority-low "; + elseif ($dao->priority == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Low')) { + $caseActivities[$caseActivityId]['DT_RowClass'] .= " priority-low "; } } //Add data to the row for inline editing, via DataTable syntax - $caseActivity['DT_RowAttr'] = array(); - $caseActivity['DT_RowAttr']['data-entity'] = 'activity'; - $caseActivity['DT_RowAttr']['data-id'] = $caseActivityId; + $caseActivities[$caseActivityId]['DT_RowAttr'] = array(); + $caseActivities[$caseActivityId]['DT_RowAttr']['data-entity'] = 'activity'; + $caseActivities[$caseActivityId]['DT_RowAttr']['data-id'] = $caseActivityId; //Activity Date and Time - $caseActivity['activity_date_time'] = CRM_Utils_Date::customFormat($dao->display_date); + $caseActivities[$caseActivityId]['activity_date_time'] = CRM_Utils_Date::customFormat($dao->display_date); //Activity Subject - $caseActivity['subject'] = $dao->subject; + $caseActivities[$caseActivityId]['subject'] = $dao->subject; //Activity Type - $caseActivity['type'] = (!empty($activityTypes[$dao->type]['icon']) ? ' ' : '') + $caseActivities[$caseActivityId]['type'] = (!empty($activityTypes[$dao->type]['icon']) ? ' ' : '') . $activityTypes[$dao->type]['label']; - //Activity Target (With) - $targetContact = ''; - if (isset($dao->target_contact_id)) { - $targetContact = $dao->target_contact_name; - if ($hasViewContact) { - $targetContact = '' . $dao->target_contact_name . ''; + // Activity Target (With Contact) (There can be more than one) + $targetContact = self::formatContactLink($dao->target_contact_id, $dao->target_contact_name); + if (empty($caseActivities[$caseActivityId]['target_contact_name'])) { + $caseActivities[$caseActivityId]['target_contact_name'] = $targetContact; + } + else { + if (strpos($caseActivities[$caseActivityId]['target_contact_name'], $targetContact) === FALSE) { + $caseActivities[$caseActivityId]['target_contact_name'] .= '; ' . $targetContact; } } - $caseActivity['target_contact_name'] = $targetContact; - //Activity Source Contact (Reporter) - $sourceContact = $dao->source_contact_name; - if ($hasViewContact) { - $sourceContact = '' . $dao->source_contact_name . ''; - } - $caseActivity['source_contact_name'] = $sourceContact; + // Activity Source Contact (Reporter) (There can only be one) + $sourceContact = self::formatContactLink($dao->source_contact_id, $dao->source_contact_name); + $caseActivities[$caseActivityId]['source_contact_name'] = $sourceContact; - //Activity Assignee. CRM-4485. - $assigneeContact = ''; - if (isset($dao->assignee_contact_id)) { - $assigneeContact = $dao->assignee_contact_name; - if ($hasViewContact) { - $assigneeContact = '' . $dao->assignee_contact_name . ''; - } + // Activity Assignee (There can be more than one) + $assigneeContact = self::formatContactLink($dao->assignee_contact_id, $dao->assignee_contact_name); + if (empty($caseActivities[$caseActivityId]['assignee_contact_name'])) { + $caseActivities[$caseActivityId]['assignee_contact_name'] = $assigneeContact; } - $caseActivity['assignee_contact_name'] = $assigneeContact; - - //Activity Status - $caseActivity['status_id'] = $activityStatuses[$dao->status]; - - // FIXME: Why are we not using CRM_Core_Action for these links? This is too much manual work and likely to get out-of-sync with core markup. - $url = ""; - $css = 'class="action-item crm-hover-button"'; - if ($allowView) { - $viewUrl = CRM_Utils_System::url('civicrm/case/activity/view', array('cid' => $contactID, 'aid' => $caseActivityId)); - $url = '' . ts('View') . ''; - } - $additionalUrl = "&id={$caseActivityId}"; - if (!$dao->deleted) { - //hide edit link of activity type email.CRM-4530. - if (!in_array($dao->type, $emailActivityTypeIDs)) { - //hide Edit link if activity type is NOT editable (special case activities).CRM-5871 - if ($allowEdit) { - $url .= '' . ts('Edit') . ' '; - } - } - if ($allowDelete) { - $url .= ' ' . ts('Delete') . ''; + else { + if (strpos($caseActivities[$caseActivityId]['assignee_contact_name'], $assigneeContact) === FALSE) { + $caseActivities[$caseActivityId]['assignee_contact_name'] .= '; ' . $assigneeContact; } } - elseif (!$caseDeleted) { - $url = ' ' . ts('Restore') . ''; - $caseActivity['status_id'] = $caseActivity['status_id'] . '
    (deleted)'; - } - //check for operations. - if (self::checkPermission($caseActivityId, 'Move To Case', $dao->activity_type_id)) { - $url .= ' ' . ts('Move To Case') . ' '; - } - if (self::checkPermission($caseActivityId, 'Copy To Case', $dao->activity_type_id)) { - $url .= ' ' . ts('Copy To Case') . ' '; + // Activity Status Label for Case activities list + $deleted = ''; + if ($dao->deleted) { + $deleted = '
    ' . ts('(deleted)'); } - // if there are file attachments we will return how many and, if only one, add a link to it + $caseActivities[$caseActivityId]['status_id'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_status_id', $dao->status) . $deleted; + // if there are file attachments we will return how many if (!empty($dao->attachment_ids)) { $attachmentIDs = array_unique(explode(',', $dao->attachment_ids)); $caseActivity['no_attachments'] = count($attachmentIDs); - $url .= implode(' ', CRM_Core_BAO_File::paperIconAttachment('civicrm_activity', $caseActivityId)); } - $caseActivity['links'] = $url; - - array_push($caseActivities, $caseActivity); + $caseActivities[$caseActivityId]['links'] = CRM_Case_Selector_Search::addCaseActivityLinks($caseID, $contactID, $userID, $context, $dao); } - $dao->free(); $caseActivitiesDT = array(); - $caseActivitiesDT['data'] = $caseActivities; + $caseActivitiesDT['data'] = array_values($caseActivities); $caseActivitiesDT['recordsTotal'] = $caseCount; $caseActivitiesDT['recordsFiltered'] = $caseCount; return $caseActivitiesDT; } + /** + * Helper function to generate a formatted contact link/name for display in the Case activities tab + * + * @param $contactId + * @param $contactName + * + * @return string + */ + private static function formatContactLink($contactId, $contactName) { + if (empty($contactId)) { + return NULL; + } + + $hasViewContact = CRM_Contact_BAO_Contact_Permission::allow($contactId); + + if ($hasViewContact) { + $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid={$contactId}"); + return "" . $contactName . ""; + } + else { + return $contactName; + } + } + /** * Get Case Related Contacts. * @@ -1206,26 +1146,53 @@ public static function getRelatedContacts($caseID, $includeDetails = TRUE) { $caseInfo = civicrm_api3('Case', 'getsingle', array( 'id' => $caseID, // Most efficient way of retrieving definition is to also include case type id and name so the api doesn't have to look it up separately - 'return' => array('case_type_id', 'case_type_id.name', 'case_type_id.definition'), + 'return' => array('case_type_id', 'case_type_id.name', 'case_type_id.definition', 'contact_id'), )); if (!empty($caseInfo['case_type_id.definition']['caseRoles'])) { $caseRoles = CRM_Utils_Array::rekey($caseInfo['case_type_id.definition']['caseRoles'], 'name'); } } - $values = array(); - $query = ' - SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_b_a as role, crt.name_b_a, ce.email - FROM civicrm_relationship cr - LEFT JOIN civicrm_relationship_type crt - ON crt.id = cr.relationship_type_id - LEFT JOIN civicrm_contact cc - ON cc.id = cr.contact_id_b - LEFT JOIN civicrm_email ce - ON ce.contact_id = cc.id - AND ce.is_primary= 1 - WHERE cr.case_id = %1 AND cr.is_active AND cc.is_deleted <> 1'; - $params = array(1 => array($caseID, 'Integer')); + $values = array(); + $query = << 1 + LEFT JOIN civicrm_email ce + ON ce.contact_id = cc.id + AND ce.is_primary= 1 + LEFT JOIN civicrm_phone cp + ON cp.contact_id = cc.id + AND cp.is_primary= 1 + WHERE cr.case_id = %1 + AND cr.is_active + AND cc.id NOT IN (%2) + UNION + SELECT cc.display_name as name, cc.sort_name as sort_name, cc.id, cr.relationship_type_id, crt.label_a_b as role, crt.name_a_b as role_name, ce.email, cp.phone + FROM civicrm_relationship cr + JOIN civicrm_relationship_type crt + ON crt.id = cr.relationship_type_id + JOIN civicrm_contact cc + ON cc.id = cr.contact_id_b + AND cc.is_deleted <> 1 + LEFT JOIN civicrm_email ce + ON ce.contact_id = cc.id + AND ce.is_primary= 1 + LEFT JOIN civicrm_phone cp + ON cp.contact_id = cc.id + AND cp.is_primary= 1 + WHERE cr.case_id = %1 + AND cr.is_active + AND cc.id NOT IN (%2) +HERESQL; + $params = array( + 1 => array($caseID, 'Integer'), + 2 => array(implode(',', $caseInfo['client_id']), 'String'), + ); $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { @@ -1240,9 +1207,10 @@ public static function getRelatedContacts($caseID, $includeDetails = TRUE) { 'relationship_type_id' => $dao->relationship_type_id, 'role' => $dao->role, 'email' => $dao->email, + 'phone' => $dao->phone, ); // Add more info about the role (creator, manager) - $role = CRM_Utils_Array::value($dao->name_b_a, $caseRoles); + $role = CRM_Utils_Array::value($dao->role_name, $caseRoles); if ($role) { unset($role['name']); $details += $role; @@ -1250,7 +1218,6 @@ public static function getRelatedContacts($caseID, $includeDetails = TRUE) { $values[] = $details; } } - $dao->free(); return $values; } @@ -1275,9 +1242,9 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta } $tplParams = $activityInfo = array(); - //if its a case activity + $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id'); + // If it's a case activity if ($caseId) { - $activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityId, 'activity_type_id'); $nonCaseActivityTypes = CRM_Core_PseudoConstant::activityType(); if (!empty($nonCaseActivityTypes[$activityTypeId])) { $anyActivity = TRUE; @@ -1301,6 +1268,7 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta if ($caseId) { $activityInfo['fields'][] = array('label' => 'Case ID', 'type' => 'String', 'value' => $caseId); } + $tplParams['activityTypeName'] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_DAO_Activity', 'activity_type_id', $activityTypeId); $tplParams['activity'] = $activityInfo; foreach ($tplParams['activity']['fields'] as $k => $val) { if (CRM_Utils_Array::value('label', $val) == ts('Subject')) { @@ -1319,10 +1287,10 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta $activityParams['source_record_id'] = $activityId; $activityParams['source_contact_id'] = $userID; - $activityParams['activity_type_id'] = CRM_Core_OptionGroup::getValue('activity_type', 'Email', 'name'); + $activityParams['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_type_id', 'Email'); $activityParams['activity_date_time'] = date('YmdHis'); - $activityParams['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); - $activityParams['medium_id'] = CRM_Core_OptionGroup::getValue('encounter_medium', 'email', 'name'); + $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_status_id', 'Completed'); + $activityParams['medium_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'encounter_medium', 'email'); $activityParams['case_id'] = $caseId; $activityParams['is_auto'] = 0; $activityParams['target_id'] = $clientId; @@ -1359,7 +1327,7 @@ public static function sendActivityCopy($clientId, $activityId, $contacts, $atta ) ); - $activityParams['subject'] = $activitySubject . ' - copy sent to ' . $displayName; + $activityParams['subject'] = ts('%1 - copy sent to %2', [1 => $activitySubject, 2 => $displayName]); $activityParams['details'] = $message; if (!empty($result[$info['contact_id']])) { @@ -1480,10 +1448,7 @@ public static function recordActivityViaEmail($file) { $params['activity_date_time'] = $result['date']; $params['details'] = $result['body']; $params['source_contact_id'] = $result['from']['id']; - $params['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', - 'Completed', - 'name' - ); + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $details = CRM_Case_PseudoConstant::caseActivityType(); $matches = array(); @@ -1498,7 +1463,7 @@ public static function recordActivityViaEmail($file) { } } if (!isset($params['activity_type_id'])) { - $params['activity_type_id'] = CRM_Core_OptionGroup::getValue('activity_type', 'Inbound Email', 'name'); + $params['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Inbound Email'); } // create activity @@ -1538,24 +1503,24 @@ public static function getNextScheduledActivity($cases, $type = 'upcoming') { $caseID = implode(',', $cases['case_id']); $contactID = implode(',', $cases['contact_id']); - $condition = " - AND civicrm_case_contact.contact_id IN( {$contactID} ) + $condition = " civicrm_case_contact.contact_id IN( {$contactID} ) AND civicrm_case.id IN( {$caseID}) AND civicrm_case.is_deleted = {$cases['case_deleted']}"; - $query = self::getCaseActivityQuery($type, $userID, $condition, $cases['case_deleted']); + $query = self::getCaseActivityQuery($type, $userID, $condition); + $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id'); $res = CRM_Core_DAO::executeQuery($query); $activityInfo = array(); while ($res->fetch()) { if ($type == 'upcoming') { - $activityInfo[$res->case_id]['date'] = $res->case_scheduled_activity_date; - $activityInfo[$res->case_id]['type'] = $res->case_scheduled_activity_type; + $activityInfo[$res->case_id]['date'] = $res->activity_date_time; + $activityInfo[$res->case_id]['type'] = CRM_Utils_Array::value($res->activity_type_id, $activityTypes); } else { - $activityInfo[$res->case_id]['date'] = $res->case_recent_activity_date; - $activityInfo[$res->case_id]['type'] = $res->case_recent_activity_type; + $activityInfo[$res->case_id]['date'] = $res->activity_date_time; + $activityInfo[$res->case_id]['type'] = CRM_Utils_Array::value($res->activity_type_id, $activityTypes); } } @@ -1738,7 +1703,6 @@ public static function getCaseActivityDates($caseID, $criteriaParams = array(), $values[$dao->id]['id'] = $dao->id; $values[$dao->id]['activity_date'] = $dao->activity_date; } - $dao->free(); return $values; } @@ -1801,23 +1765,16 @@ public static function createCaseRoleActivity($caseId, $relationshipId, $relCont 'source_contact_id' => $session->get('userID'), 'subject' => $caseRelationship . ' : ' . $assigneContactName, 'activity_date_time' => date('YmdHis'), - 'status_id' => CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'), + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), ); //if $relContactId is passed, role is added or modified. if (!empty($relContactId)) { $activityParams['assignee_contact_id'] = $assigneContactIds; - - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', - 'Assign Case Role', - 'name' - ); + $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Assign Case Role'); } else { - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', - 'Remove Case Role', - 'name' - ); + $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Remove Case Role'); } $activityParams['activity_type_id'] = $activityTypeID; @@ -1842,8 +1799,8 @@ public static function createCaseRoleActivity($caseId, $relationshipId, $relCont * @param int $caseId * Case id. * - * @return array - * array of contact on success otherwise empty + * @return string + * html hyperlink of manager contact view page * */ public static function getCaseManagerContact($caseType, $caseId) { @@ -1851,33 +1808,46 @@ public static function getCaseManagerContact($caseType, $caseId) { return NULL; } - $caseManagerContact = array(); + $caseManagerName = '---'; $xmlProcessor = new CRM_Case_XMLProcessor_Process(); $managerRoleId = $xmlProcessor->getCaseManagerRoleId($caseType); if (!empty($managerRoleId)) { - $managerRoleQuery = " -SELECT civicrm_contact.id as casemanager_id, - civicrm_contact.sort_name as casemanager - FROM civicrm_contact - LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) - LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id - WHERE civicrm_case.id = %2 AND is_active = 1"; + if (substr($managerRoleId, -4) == '_a_b') { + $managerRoleQuery = " + SELECT civicrm_contact.id as casemanager_id, + civicrm_contact.sort_name as casemanager + FROM civicrm_contact + LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active + LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id + WHERE civicrm_case.id = %2 AND is_active = 1"; + } + if (substr($managerRoleId, -4) == '_b_a') { + $managerRoleQuery = " + SELECT civicrm_contact.id as casemanager_id, + civicrm_contact.sort_name as casemanager + FROM civicrm_contact + LEFT JOIN civicrm_relationship ON (civicrm_relationship.contact_id_a = civicrm_contact.id AND civicrm_relationship.relationship_type_id = %1) AND civicrm_relationship.is_active + LEFT JOIN civicrm_case ON civicrm_case.id = civicrm_relationship.case_id + WHERE civicrm_case.id = %2 AND is_active = 1"; + } $managerRoleParams = array( - 1 => array($managerRoleId, 'Integer'), + 1 => array(substr($managerRoleId, 0, -4), 'Integer'), 2 => array($caseId, 'Integer'), ); $dao = CRM_Core_DAO::executeQuery($managerRoleQuery, $managerRoleParams); if ($dao->fetch()) { - $caseManagerContact['casemanager_id'] = $dao->casemanager_id; - $caseManagerContact['casemanager'] = $dao->casemanager; + $caseManagerName = sprintf('%s', + CRM_Utils_System::url('civicrm/contact/view', array('cid' => $dao->casemanager_id)), + $dao->casemanager + ); } } - return $caseManagerContact; + return $caseManagerName; } /** @@ -1904,30 +1874,24 @@ public static function caseCount($contactId = NULL, $excludeDeleted = TRUE) { } /** - * Retrieve related cases for give case. + * Retrieve related case ids for given case. * - * @param int $mainCaseId - * Id of main case. - * @param int $contactId - * Id of contact. + * @param int $caseId * @param bool $excludeDeleted * Do not include deleted cases. * * @return array */ - public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted = TRUE) { + public static function getRelatedCaseIds($caseId, $excludeDeleted = TRUE) { //FIXME : do check for permissions. - $relatedCases = array(); - if (!$mainCaseId || !$contactId) { - return $relatedCases; + if (!$caseId) { + return array(); } - $linkActType = array_search('Link Cases', - CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name') - ); + $linkActType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Link Cases'); if (!$linkActType) { - return $relatedCases; + return array(); } $whereClause = "mainCase.id = %2"; @@ -1935,7 +1899,6 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted $whereClause .= " AND ( relAct.is_deleted = 0 OR relAct.is_deleted IS NULL )"; } - //1. first fetch related case ids. $query = " SELECT relCaseAct.case_id FROM civicrm_case mainCase @@ -1947,17 +1910,31 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted $dao = CRM_Core_DAO::executeQuery($query, array( 1 => array($linkActType, 'Integer'), - 2 => array($mainCaseId, 'Integer'), + 2 => array($caseId, 'Integer'), )); $relatedCaseIds = array(); while ($dao->fetch()) { $relatedCaseIds[$dao->case_id] = $dao->case_id; } - $dao->free(); - // there are no related cases. - if (empty($relatedCaseIds)) { - return $relatedCases; + return array_values($relatedCaseIds); + } + + /** + * Retrieve related case details for given case. + * + * @param int $caseId + * @param bool $excludeDeleted + * Do not include deleted cases. + * + * @return array + */ + public static function getRelatedCases($caseId, $excludeDeleted = TRUE) { + $relatedCaseIds = self::getRelatedCaseIds($caseId, $excludeDeleted); + $relatedCases = array(); + + if (!$relatedCaseIds) { + return array(); } $whereClause = 'relCase.id IN ( ' . implode(',', $relatedCaseIds) . ' )'; @@ -1970,8 +1947,7 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted $doFilterCases = FALSE; if (!CRM_Core_Permission::check('access all cases and activities')) { $doFilterCases = TRUE; - $session = CRM_Core_Session::singleton(); - $filterCases = CRM_Case_BAO_Case::getCases(FALSE, $session->get('userID')); + $filterCases = CRM_Case_BAO_Case::getCases(FALSE); } //2. fetch the details of related cases. @@ -1979,7 +1955,8 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted SELECT relCase.id as id, civicrm_case_type.title as case_type, client.display_name as client_name, - client.id as client_id + client.id as client_id, + relCase.status_id FROM civicrm_case relCase INNER JOIN civicrm_case_contact relCaseContact ON ( relCase.id = relCaseContact.case_id ) INNER JOIN civicrm_contact client ON ( client.id = relCaseContact.contact_id ) @@ -1989,6 +1966,7 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted $dao = CRM_Core_DAO::executeQuery($query); $contactViewUrl = CRM_Utils_System::url("civicrm/contact/view", "reset=1&cid="); $hasViewContact = CRM_Core_Permission::giveMeAllACLs(); + $statuses = CRM_Case_BAO_Case::buildOptions('status_id'); while ($dao->fetch()) { $caseView = NULL; @@ -2006,10 +1984,10 @@ public static function getRelatedCases($mainCaseId, $contactId, $excludeDeleted 'case_id' => $dao->id, 'case_type' => $dao->case_type, 'client_name' => $clientView, + 'case_status' => $statuses[$dao->status_id], 'links' => $caseView, ); } - $dao->free(); return $relatedCases; } @@ -2044,7 +2022,8 @@ public static function mergeContacts($mainContactId, $otherContactId) { * * @param bool $changeClient * - * @return int|NULL + * @return int|null + * @throws \CRM_Core_Exception */ public static function mergeCases( $mainContactId, $mainCaseId = NULL, $otherContactId = NULL, @@ -2070,9 +2049,9 @@ public static function mergeCases( return $mainCaseIds; } - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name'); - $activityStatuses = CRM_Core_PseudoConstant::activityStatus('name'); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate'); + $completedActivityStatus = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -2105,30 +2084,6 @@ public static function mergeCases( continue; } - // CRM-11662 Copy Case custom data - $extends = array('case'); - $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); - if ($groupTree) { - foreach ($groupTree as $groupID => $group) { - $table[$groupTree[$groupID]['table_name']] = array('entity_id'); - foreach ($group['fields'] as $fieldID => $field) { - $table[$groupTree[$groupID]['table_name']][] = $groupTree[$groupID]['fields'][$fieldID]['column_name']; - } - } - - foreach ($table as $tableName => $tableColumns) { - $insert = 'INSERT INTO ' . $tableName . ' (' . implode(', ', $tableColumns) . ') '; - $tableColumns[0] = $mainCaseId; - $select = 'SELECT ' . implode(', ', $tableColumns); - $from = ' FROM ' . $tableName; - $where = " WHERE {$tableName}.entity_id = {$otherCaseId}"; - $query = $insert . $select . $from . $where; - $dao = CRM_Core_DAO::executeQuery($query); - } - } - - $mainCase->free(); - $mainCaseIds[] = $mainCaseId; //insert record for case contact. $otherCaseContact = new CRM_Case_DAO_CaseContact(); @@ -2145,9 +2100,7 @@ public static function mergeCases( if (!$mainCaseContact->find(TRUE)) { $mainCaseContact->save(); } - $mainCaseContact->free(); } - $otherCaseContact->free(); } elseif (!$otherContactId) { $otherContactId = $mainContactId; @@ -2183,7 +2136,6 @@ public static function mergeCases( while ($dao->fetch()) { $singletonActivityIds[] = $dao->id; } - $dao->free(); } } @@ -2232,8 +2184,6 @@ public static function mergeCases( // insert log of all activities CRM_Activity_BAO_Activity::logActivityAction($mainActivity); - $otherActivity->free(); - $mainActivity->free(); $copiedActivityIds[] = $otherActivityId; //create case activity record. @@ -2241,7 +2191,6 @@ public static function mergeCases( $mainCaseActivity->case_id = $mainCaseId; $mainCaseActivity->activity_id = $mainActivityId; $mainCaseActivity->save(); - $mainCaseActivity->free(); //migrate source activity. $otherSourceActivity = new CRM_Activity_DAO_ActivityContact(); @@ -2260,9 +2209,7 @@ public static function mergeCases( if (!$mainActivitySource->find(TRUE)) { $mainActivitySource->save(); } - $mainActivitySource->free(); } - $otherSourceActivity->free(); //migrate target activities. $otherTargetActivity = new CRM_Activity_DAO_ActivityContact(); @@ -2281,9 +2228,7 @@ public static function mergeCases( if (!$mainActivityTarget->find(TRUE)) { $mainActivityTarget->save(); } - $mainActivityTarget->free(); } - $otherTargetActivity->free(); //migrate assignee activities. $otherAssigneeActivity = new CRM_Activity_DAO_ActivityContact(); @@ -2302,9 +2247,7 @@ public static function mergeCases( if (!$mainAssigneeActivity->find(TRUE)) { $mainAssigneeActivity->save(); } - $mainAssigneeActivity->free(); } - $otherAssigneeActivity->free(); // copy custom fields and attachments $aparams = array( @@ -2350,14 +2293,12 @@ public static function mergeCases( if (!$mainRelationship->find(TRUE)) { $mainRelationship->save(); } - $mainRelationship->free(); //get the other relationship ids to update end date. if ($updateOtherRel) { $otherRelationshipIds[$otherRelationship->id] = $otherRelationship->id; } } - $otherRelationship->free(); //update other relationships end dates if (!empty($otherRelationshipIds)) { @@ -2419,11 +2360,11 @@ public static function mergeCases( } } - //Create merge activity record. Source for merge activity is the logged in user's contact ID ($currentUserId). + // Create merge activity record. Source for merge activity is the logged in user's contact ID ($currentUserId). $activityParams = array( 'subject' => $mergeActSubject, 'details' => $mergeActSubjectDetails, - 'status_id' => array_search('Completed', $activityStatuses), + 'status_id' => $completedActivityStatus, 'activity_type_id' => $mergeActType, 'source_contact_id' => $currentUserId, 'activity_date_time' => date('YmdHis'), @@ -2434,7 +2375,6 @@ public static function mergeCases( if (!$mergeActivityId) { continue; } - $mergeActivity->free(); //connect merge activity to case. $mergeCaseAct = array( @@ -2610,7 +2550,7 @@ public static function checkPermission($activityId, $operation, $actTypeId = NUL //edit - contact must be source or assignee //view - contact must be source/assignee/target $isTarget = $isAssignee = $isSource = FALSE; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); @@ -2669,10 +2609,7 @@ public static function checkPermission($activityId, $operation, $actTypeId = NUL //do further only when operation is granted. if ($allow) { - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name'); - - //get the activity type name. - $actTypeName = CRM_Utils_Array::value($actTypeId, $activityTypes); + $actTypeName = CRM_Core_PseudoConstant::getName('CRM_Activity_BAO_Activity', 'activity_type_id', $actTypeId); //do not allow multiple copy / edit action. $singletonNames = array( @@ -2691,6 +2628,10 @@ public static function checkPermission($activityId, $operation, $actTypeId = NUL //allow edit operation. $allowEditNames = array('Open Case'); + if (CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions()) { + $allowEditNames[] = 'Inbound Email'; + } + // do not allow File on Case $doNotFileNames = array( 'Open Case', @@ -2979,15 +2920,15 @@ public static function createCaseViews() { */ public static function createCaseViewsQuery($section = 'upcoming') { $sql = ""; - $scheduled_id = CRM_Core_Pseudoconstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled'); + $scheduled_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled'); switch ($section) { case 'upcoming': $sql = "CREATE OR REPLACE VIEW `civicrm_view_case_activity_upcoming` AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id FROM civicrm_case_activity ca INNER JOIN civicrm_activity a ON ca.activity_id=a.id - WHERE a.activity_date_time = -(SELECT b.activity_date_time FROM civicrm_case_activity bca + WHERE a.id = +(SELECT b.id FROM civicrm_case_activity bca INNER JOIN civicrm_activity b ON bca.activity_id=b.id WHERE b.activity_date_time <= DATE_ADD( NOW(), INTERVAL 14 DAY ) AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id = $scheduled_id @@ -2999,8 +2940,8 @@ public static function createCaseViewsQuery($section = 'upcoming') { AS SELECT ca.case_id, a.id, a.activity_date_time, a.status_id, a.activity_type_id FROM civicrm_case_activity ca INNER JOIN civicrm_activity a ON ca.activity_id=a.id - WHERE a.activity_date_time = -(SELECT b.activity_date_time FROM civicrm_case_activity bca + WHERE a.id = +(SELECT b.id FROM civicrm_case_activity bca INNER JOIN civicrm_activity b ON bca.activity_id=b.id WHERE b.activity_date_time >= DATE_SUB( NOW(), INTERVAL 14 DAY ) AND b.is_current_revision = 1 AND b.is_deleted=0 AND b.status_id <> $scheduled_id @@ -3043,7 +2984,6 @@ public static function addCaseRelationships($caseId, $contactId) { if (!$newRelationship->find(TRUE)) { $newRelationship->save(); } - $newRelationship->free(); // store relationship type of newly created relationship $relationshipTypes[] = $caseRelationships->relationship_type_id; @@ -3122,6 +3062,17 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( case 'medium_id': $className = 'CRM_Activity_BAO_Activity'; break; + + // Filter status id by case type id + case 'status_id': + if (!empty($props['case_type_id'])) { + $idField = is_numeric($props['case_type_id']) ? 'id' : 'name'; + $caseType = civicrm_api3('CaseType', 'getsingle', array($idField => $props['case_type_id'], 'return' => 'definition')); + if (!empty($caseType['definition']['statuses'])) { + $params['condition'] = 'v.name IN ("' . implode('","', $caseType['definition']['statuses']) . '")'; + } + } + break; } return CRM_Core_PseudoConstant::get($className, $fieldName, $params, $context); } @@ -3172,7 +3123,9 @@ public function addSelectWhereClause() { public static function getReceiptFrom($activityID) { $name = $address = NULL; - if (!empty($activityID)) { + if (!empty($activityID) && (Civi::settings()->get('allow_mail_from_logged_in_contact'))) { + // This breaks SPF/DMARC if email is sent from an email address that the server is not authorised to send from. + // so we can disable this behaviour with the "allow_mail_from_logged_in_contact" setting. // There is always a 'Added by' contact for a activity, // so we can safely use ActivityContact.Getvalue API $sourceContactId = civicrm_api3('ActivityContact', 'getvalue', array( @@ -3192,4 +3145,82 @@ public static function getReceiptFrom($activityID) { return "$name <$address>"; } + /** + * @return array + */ + public static function getEntityRefFilters() { + $filters = [ + [ + 'key' => 'case_id.case_type_id', + 'value' => ts('Case Type'), + 'entity' => 'Case', + ], + [ + 'key' => 'case_id.status_id', + 'value' => ts('Case Status'), + 'entity' => 'Case', + ], + ]; + foreach (CRM_Contact_BAO_Contact::getEntityRefFilters() as $filter) { + $filter += ['entity' => 'Contact']; + $filter['key'] = 'contact_id.' . $filter['key']; + $filters[] = $filter; + } + return $filters; + } + + /** + * Fetch Case Role direction from Case Type + */ + public static function getCaseRoleDirection($caseId, $roleTypeId = NULL) { + try { + $case = civicrm_api3('Case', 'getsingle', array('id' => $caseId)); + } + catch (CiviCRM_API3_Exception $e) { + // Lack of permissions will throw an exception + return 0; + } + if (!empty($case['case_type_id'])) { + try { + $caseType = civicrm_api3('CaseType', 'getsingle', array('id' => $case['case_type_id'], 'return' => array('definition'))); + } + catch (CiviCRM_API3_Exception $e) { + // Lack of permissions will throw an exception + return 'no case type found'; + } + if (!empty($caseType['definition']['caseRoles'])) { + $caseRoles = array(); + foreach ($caseType['definition']['caseRoles'] as $key => $roleDetails) { + // Check if its an a_b label + try { + $relType = civicrm_api3('RelationshipType', 'getsingle', array('label_a_b' => $roleDetails['name'])); + } + catch (CiviCRM_API3_Exception $e) { + } + if (!empty($relType['id'])) { + $roleDetails['id'] = $relType['id']; + $roleDetails['direction'] = 'b_a'; + } + // Check if its a b_a label + try { + $relTypeBa = civicrm_api3('RelationshipType', 'getsingle', array('label_b_a' => $roleDetails['name'])); + } + catch (CiviCRM_API3_Exception $e) { + } + if (!empty($relTypeBa['id'])) { + if (!empty($roleDetails['direction'])) { + $roleDetails['direction'] = 'bidrectional'; + } + else { + $roleDetails['id'] = $relTypeBa['id']; + $roleDetails['direction'] = 'a_b'; + } + } + $caseRoles[$roleDetails['id']] = $roleDetails; + } + } + return $caseRoles; + } + } + } diff --git a/CRM/Case/BAO/CaseContact.php b/CRM/Case/BAO/CaseContact.php index ea85094adc0e..9d8a3955203d 100644 --- a/CRM/Case/BAO/CaseContact.php +++ b/CRM/Case/BAO/CaseContact.php @@ -1,9 +1,9 @@ copyValues($params); $caseContact->save(); + CRM_Utils_Hook::post($hook, 'CaseContact', $caseContact->id, $caseContact); + // add to recently viewed $caseType = CRM_Case_BAO_Case::getCaseType($caseContact->case_id); $url = CRM_Utils_System::url('civicrm/contact/view/case', @@ -57,7 +62,7 @@ public static function create($params) { $title = CRM_Contact_BAO_Contact::displayName($caseContact->contact_id) . ' - ' . $caseType; - $recentOther = array(); + $recentOther = []; if (CRM_Core_Permission::checkActionPermission('CiviCase', CRM_Core_Action::DELETE)) { $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/case', "action=delete&reset=1&id={$caseContact->case_id}&cid={$caseContact->contact_id}&context=home" @@ -81,12 +86,12 @@ public static function create($params) { * @inheritDoc */ public function addSelectWhereClause() { - return array( + return [ // Reuse case acls 'case_id' => CRM_Utils_SQL::mergeSubquery('Case'), // Case acls already check for contact access so we can just mark contact_id as handled - 'contact_id' => array(), - ); + 'contact_id' => [], + ]; // Don't call hook selectWhereClause, the case query already did } diff --git a/CRM/Case/BAO/CaseType.php b/CRM/Case/BAO/CaseType.php index 65a49863bfaf..7a107d2b15db 100644 --- a/CRM/Case/BAO/CaseType.php +++ b/CRM/Case/BAO/CaseType.php @@ -1,9 +1,9 @@ copyValues($params); - return $caseTypeDAO->save(); + $result = $caseTypeDAO->save(); + CRM_Case_XMLRepository::singleton()->flush(); + return $result; } /** @@ -100,7 +102,6 @@ protected function assignTestValue($fieldName, &$fieldDef, $counter) { } } - /** * Format / convert submitted array to xml for case type definition * @@ -158,7 +159,8 @@ public static function convertDefinitionToXML($name, $definition) { } break; - case 'sequence': // passthrough + // passthrough + case 'sequence': case 'timeline': if ($setVal) { $xmlFile .= "<{$index}>true\n"; @@ -188,7 +190,20 @@ public static function convertDefinitionToXML($name, $definition) { $xmlFile .= "\n"; } + if (array_key_exists('restrictActivityAsgmtToCmsUser', $definition)) { + $xmlFile .= "" . $definition['restrictActivityAsgmtToCmsUser'] . "\n"; + } + + if (!empty($definition['activityAsgmtGrps'])) { + $xmlFile .= "\n"; + foreach ($definition['activityAsgmtGrps'] as $value) { + $xmlFile .= "$value\n"; + } + $xmlFile .= "\n"; + } + $xmlFile .= ''; + return $xmlFile; } @@ -204,7 +219,12 @@ public static function convertDefinitionToXML($name, $definition) { */ protected static function encodeXmlString($str) { // PHP 5.4: return htmlspecialchars($str, ENT_XML1, 'UTF-8') - return htmlspecialchars($str); + if (is_scalar($str)) { + return htmlspecialchars($str); + } + else { + return NULL; + } } /** @@ -218,15 +238,23 @@ protected static function encodeXmlString($str) { */ public static function convertXmlToDefinition($xml) { // build PHP array based on definition - $definition = array(); + $definition = []; if (isset($xml->forkable)) { $definition['forkable'] = (int) $xml->forkable; } + if (isset($xml->RestrictActivityAsgmtToCmsUser)) { + $definition['restrictActivityAsgmtToCmsUser'] = (int) $xml->RestrictActivityAsgmtToCmsUser; + } + + if (isset($xml->ActivityAsgmtGrps)) { + $definition['activityAsgmtGrps'] = (array) $xml->ActivityAsgmtGrps->Group; + } + // set activity types if (isset($xml->ActivityTypes)) { - $definition['activityTypes'] = array(); + $definition['activityTypes'] = []; foreach ($xml->ActivityTypes->ActivityType as $activityTypeXML) { $definition['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE); } @@ -239,10 +267,12 @@ public static function convertXmlToDefinition($xml) { // set activity sets if (isset($xml->ActivitySets)) { - $definition['activitySets'] = array(); + $definition['activitySets'] = []; + $definition['timelineActivityTypes'] = []; + foreach ($xml->ActivitySets->ActivitySet as $activitySetXML) { // parse basic properties - $activitySet = array(); + $activitySet = []; $activitySet['name'] = (string) $activitySetXML->name; $activitySet['label'] = (string) $activitySetXML->label; if ('true' == (string) $activitySetXML->timeline) { @@ -253,9 +283,13 @@ public static function convertXmlToDefinition($xml) { } if (isset($activitySetXML->ActivityTypes)) { - $activitySet['activityTypes'] = array(); + $activitySet['activityTypes'] = []; foreach ($activitySetXML->ActivityTypes->ActivityType as $activityTypeXML) { - $activitySet['activityTypes'][] = json_decode(json_encode($activityTypeXML), TRUE); + $activityType = json_decode(json_encode($activityTypeXML), TRUE); + $activitySet['activityTypes'][] = $activityType; + if ($activitySetXML->timeline) { + $definition['timelineActivityTypes'][] = $activityType; + } } } $definition['activitySets'][] = $activitySet; @@ -264,7 +298,7 @@ public static function convertXmlToDefinition($xml) { // set case roles if (isset($xml->CaseRoles)) { - $definition['caseRoles'] = array(); + $definition['caseRoles'] = []; foreach ($xml->CaseRoles->RelationshipType as $caseRoleXml) { $definition['caseRoles'][] = json_decode(json_encode($caseRoleXml), TRUE); } @@ -329,6 +363,7 @@ public static function &create(&$params) { } $transaction->commit(); CRM_Case_XMLRepository::singleton(TRUE); + CRM_Core_OptionGroup::flushAll(); return $caseType; } @@ -363,7 +398,7 @@ public static function del($caseTypeId) { $refCounts = $caseType->getReferenceCounts(); $total = array_sum(CRM_Utils_Array::collect('count', $refCounts)); if ($total) { - throw new CRM_Core_Exception(ts("You can not delete this case type -- it is assigned to %1 existing case record(s). If you do not want this case type to be used going forward, consider disabling it instead.", array(1 => $total))); + throw new CRM_Core_Exception(ts("You can not delete this case type -- it is assigned to %1 existing case record(s). If you do not want this case type to be used going forward, consider disabling it instead.", [1 => $total])); } $result = $caseType->delete(); CRM_Case_XMLRepository::singleton(TRUE); diff --git a/CRM/Case/BAO/Query.php b/CRM/Case/BAO/Query.php index 92358ef52144..3db0adee6001 100644 --- a/CRM/Case/BAO/Query.php +++ b/CRM/Case/BAO/Query.php @@ -1,9 +1,9 @@ _returnProperties['case_role'])) { - $query->_select['case_role'] = "case_relation_type.label_b_a as case_role"; + $query->_select['case_role'] = "IF(case_relationship.contact_id_b = contact_a.id, case_relation_type.label_b_a, case_relation_type.label_a_b) as case_role"; $query->_element['case_role'] = 1; $query->_tables['case_relationship'] = $query->_whereTables['case_relationship'] = 1; $query->_tables['case_relation_type'] = $query->_whereTables['case_relation_type'] = 1; @@ -261,7 +261,7 @@ public static function where(&$query) { */ public static function whereClauseSingle(&$values, &$query) { list($name, $op, $value, $grouping, $wildcard) = $values; - $val = $names = array(); + $val = $names = []; switch ($name) { case 'case_type_id': @@ -286,7 +286,7 @@ public static function whereClauseSingle(&$values, &$query) { $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_case.{$name}", $op, $value, "Integer"); list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Case_DAO_Case', $name, $value, $op); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $label, 2 => $op, 3 => $value)); + $query->_qill[$grouping][] = ts('%1 %2 %3', [1 => $label, 2 => $op, 3 => $value]); $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; return; @@ -296,12 +296,12 @@ public static function whereClauseSingle(&$values, &$query) { if ($value == 2) { $session = CRM_Core_Session::singleton(); $userID = $session->get('userID'); - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_b", $op, $userID, 'Int'); - $query->_qill[$grouping][] = ts('Case %1 My Cases', array(1 => $op)); + $query->_where[$grouping][] = ' (( ' . CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_b", $op, $userID, 'Int') . ' AND ' . CRM_Contact_BAO_Query::buildClause("case_relationship.is_active", '<>', 0, 'Int') . ' ) OR ( ' . CRM_Contact_BAO_Query::buildClause("case_relationship.contact_id_a", $op, $userID, 'Int') . ' AND ' . CRM_Contact_BAO_Query::buildClause("case_relationship.is_active", '<>', 0, 'Int') . ' ))'; + $query->_qill[$grouping][] = ts('Case %1 My Cases', [1 => $op]); $query->_tables['case_relationship'] = $query->_whereTables['case_relationship'] = 1; } elseif ($value == 1) { - $query->_qill[$grouping][] = ts('Case %1 All Cases', array(1 => $op)); + $query->_qill[$grouping][] = ts('Case %1 All Cases', [1 => $op]); $query->_where[$grouping][] = "civicrm_case_contact.contact_id = contact_a.id"; } $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; @@ -319,7 +319,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_activity_subject': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.subject", $op, $value, 'String'); - $query->_qill[$grouping][] = ts("Activity Subject %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Activity Subject %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; @@ -327,14 +327,14 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_subject': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_case.subject", $op, $value, 'String'); - $query->_qill[$grouping][] = ts("Case Subject %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Case Subject %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; return; case 'case_source_contact_id': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_case_reporter.sort_name", $op, $value, 'String'); - $query->_qill[$grouping][] = ts("Activity Reporter %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Activity Reporter %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case_reporter'] = $query->_whereTables['civicrm_case_reporter'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; @@ -346,7 +346,7 @@ public static function whereClauseSingle(&$values, &$query) { $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.activity_date_time", $op, $date, 'Date'); if ($date) { $date = CRM_Utils_Date::customFormat($date); - $query->_qill[$grouping][] = ts("Activity Actual Date %1 %2", array(1 => $op, 2 => $date)); + $query->_qill[$grouping][] = ts("Activity Actual Date %1 %2", [1 => $op, 2 => $date]); } $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; @@ -358,7 +358,7 @@ public static function whereClauseSingle(&$values, &$query) { $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.activity_date_time", $op, $date, 'Date'); if ($date) { $date = CRM_Utils_Date::customFormat($date); - $query->_qill[$grouping][] = ts("Activity Schedule Date %1 %2", array(1 => $op, 2 => $date)); + $query->_qill[$grouping][] = ts("Activity Schedule Date %1 %2", [1 => $op, 2 => $date]); } $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; @@ -372,7 +372,7 @@ public static function whereClauseSingle(&$values, &$query) { } $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.activity_type_id", $op, $value, 'Int'); - $query->_qill[$grouping][] = ts("Activity Type %1 %2", array(1 => $op, 2 => $names)); + $query->_qill[$grouping][] = ts("Activity Type %1 %2", [1 => $op, 2 => $names]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['case_activity_type'] = 1; @@ -386,7 +386,7 @@ public static function whereClauseSingle(&$values, &$query) { } $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.status_id", $op, $value, 'Int'); - $query->_qill[$grouping][] = ts("Activity Type %1 %2", array(1 => $op, 2 => $names)); + $query->_qill[$grouping][] = ts("Activity Type %1 %2", [1 => $op, 2 => $names]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['case_activity_status'] = 1; @@ -395,7 +395,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_activity_duration': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.duration", $op, $value, 'Int'); - $query->_qill[$grouping][] = ts("Activity Duration %1 %2", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Activity Duration %1 %2", [1 => $op, 2 => $value]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; @@ -408,7 +408,7 @@ public static function whereClauseSingle(&$values, &$query) { } $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.medium_id", $op, $value, 'Int'); - $query->_qill[$grouping][] = ts("Activity Medium %1 %2", array(1 => $op, 2 => $names)); + $query->_qill[$grouping][] = ts("Activity Medium %1 %2", [1 => $op, 2 => $names]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['case_activity_medium'] = 1; @@ -417,7 +417,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_activity_details': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.details", $op, $value, 'String'); - $query->_qill[$grouping][] = ts("Activity Details %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Activity Details %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; @@ -425,7 +425,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_activity_is_auto': $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_activity.is_auto", $op, $value, 'Boolean'); - $query->_qill[$grouping][] = ts("Activity Auto Genrated %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Activity Auto Genrated %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['case_activity'] = $query->_whereTables['case_activity'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; @@ -434,8 +434,7 @@ public static function whereClauseSingle(&$values, &$query) { // adding where clause for case_role case 'case_role': - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("case_relation_type.name_b_a", $op, $value, 'String'); - $query->_qill[$grouping][] = ts("Role in Case %1 '%2'", array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts("Role in Case %1 '%2'", [1 => $op, 2 => $value]); $query->_tables['case_relation_type'] = $query->_whereTables['case_relationship_type'] = 1; $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; @@ -467,7 +466,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'case_taglist': $taglist = $value; - $value = array(); + $value = []; foreach ($taglist as $val) { if ($val) { $val = explode(',', $val); @@ -479,19 +478,26 @@ public static function whereClauseSingle(&$values, &$query) { } } case 'case_tags': - $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); - if (is_array($value)) { - foreach ($value as $k => $v) { + if (!empty($value)) { + if (is_array($value)) { + // Search tag(s) are part of a tag set + $val = array_keys($value); + } + else { + // Search tag(s) are part of the tag tree + $val = explode(',', $value); + } + foreach ($val as $v) { if ($v) { - $val[$k] = $k; - $names[] = $tags[$k]; + $names[] = $tags[$v]; } } } $query->_where[$grouping][] = " civicrm_case_tag.tag_id IN (" . implode(',', $val) . " )"; - $query->_qill[$grouping][] = ts('Case Tags %1', array(1 => $op)) . ' ' . implode(' ' . ts('or') . ' ', $names); + $query->_qill[$grouping][] = ts('Case Tags %1', [1 => $op]) . ' ' . implode(' ' . ts('or') . ' ', $names); $query->_tables['civicrm_case'] = $query->_whereTables['civicrm_case'] = 1; $query->_tables['civicrm_case_contact'] = $query->_whereTables['civicrm_case_contact'] = 1; $query->_tables['civicrm_case_tag'] = $query->_whereTables['civicrm_case_tag'] = 1; @@ -517,7 +523,7 @@ public static function from($name, $mode, $side) { break; case 'civicrm_case_reporter': - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $from .= " $side JOIN civicrm_activity_contact as case_activity_contact ON (case_activity.id = case_activity_contact.activity_id AND case_activity_contact.record_type_id = {$sourceID} ) "; $from .= " $side JOIN civicrm_contact as civicrm_case_reporter ON case_activity_contact.contact_id = civicrm_case_reporter.id "; @@ -549,7 +555,7 @@ public static function from($name, $mode, $side) { case 'case_relationship': $session = CRM_Core_Session::singleton(); $userID = $session->get('userID'); - $from .= " $side JOIN civicrm_relationship case_relationship ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} AND case_relationship.case_id = civicrm_case.id )"; + $from .= " $side JOIN civicrm_relationship case_relationship ON ( case_relationship.contact_id_a = civicrm_case_contact.contact_id AND case_relationship.contact_id_b = {$userID} AND case_relationship.case_id = civicrm_case.id OR case_relationship.contact_id_b = civicrm_case_contact.contact_id AND case_relationship.contact_id_a = {$userID} AND case_relationship.case_id = civicrm_case.id )"; break; case 'case_relation_type': @@ -598,7 +604,7 @@ public static function defaultReturnProperties( $properties = NULL; if ($mode & CRM_Contact_BAO_Query::MODE_CASE) { - $properties = array( + $properties = [ 'contact_type' => 1, 'contact_sub_type' => 1, 'contact_id' => 1, @@ -616,7 +622,7 @@ public static function defaultReturnProperties( 'case_scheduled_activity_date' => 1, 'phone' => 1, // 'case_scheduled_activity_type'=> 1 - ); + ]; if ($includeCustomFields) { // also get all the custom case properties @@ -643,7 +649,7 @@ public static function extraReturnProperties($mode) { $properties = NULL; if ($mode & CRM_Contact_BAO_Query::MODE_CASE) { - $properties = array( + $properties = [ 'case_start_date' => 1, 'case_end_date' => 1, 'case_subject' => 1, @@ -653,7 +659,7 @@ public static function extraReturnProperties($mode) { 'case_activity_medium_id' => 1, 'case_activity_details' => 1, 'case_activity_is_auto' => 1, - ); + ]; } return $properties; } @@ -663,11 +669,11 @@ public static function extraReturnProperties($mode) { */ public static function tableNames(&$tables) { if (!empty($tables['civicrm_case'])) { - $tables = array_merge(array('civicrm_case_contact' => 1), $tables); + $tables = array_merge(['civicrm_case_contact' => 1], $tables); } if (!empty($tables['case_relation_type'])) { - $tables = array_merge(array('case_relationship' => 1), $tables); + $tables = array_merge(['case_relationship' => 1], $tables); } } @@ -681,14 +687,14 @@ public static function buildSearchForm(&$form) { $configured = CRM_Case_BAO_Case::isCaseConfigured(); $form->assign('notConfigured', !$configured['configured']); - $form->addField('case_type_id', array('context' => 'search', 'entity' => 'Case')); - $form->addField('case_status_id', array('context' => 'search', 'entity' => 'Case')); + $form->addField('case_type_id', ['context' => 'search', 'entity' => 'Case']); + $form->addField('case_status_id', ['context' => 'search', 'entity' => 'Case']); CRM_Core_Form_Date::buildDateRange($form, 'case_from', 1, '_start_date_low', '_start_date_high', ts('From'), FALSE); CRM_Core_Form_Date::buildDateRange($form, 'case_to', 1, '_end_date_low', '_end_date_high', ts('From'), FALSE); $form->addElement('hidden', 'case_from_start_date_range_error'); $form->addElement('hidden', 'case_to_end_date_range_error'); - $form->addFormRule(array('CRM_Case_BAO_Query', 'formRule'), $form); + $form->addFormRule(['CRM_Case_BAO_Query', 'formRule'], $form); $form->assign('validCiviCase', TRUE); @@ -696,17 +702,18 @@ public static function buildSearchForm(&$form) { $accessAllCases = FALSE; if (CRM_Core_Permission::check('access all cases and activities')) { $accessAllCases = TRUE; - $caseOwner = array(1 => ts('Search All Cases'), 2 => ts('Only My Cases')); + $caseOwner = [1 => ts('Search All Cases'), 2 => ts('Only My Cases')]; $form->addRadio('case_owner', ts('Cases'), $caseOwner); + if ($form->get('context') != 'dashboard') { + $form->add('checkbox', 'upcoming', ts('Search Cases with Upcoming Activities')); + } } $form->assign('accessAllCases', $accessAllCases); - $caseTags = CRM_Core_BAO_Tag::getTags('civicrm_case'); + $caseTags = CRM_Core_BAO_Tag::getColorTags('civicrm_case'); if ($caseTags) { - foreach ($caseTags as $tagID => $tagName) { - $form->_tagElement = &$form->addElement('checkbox', "case_tags[$tagID]", NULL, $tagName); - } + $form->add('select2', 'case_tags', ts('Case Tag(s)'), $caseTags, FALSE, ['class' => 'big', 'placeholder' => ts('- select -'), 'multiple' => TRUE]); } $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_case'); @@ -716,9 +723,19 @@ public static function buildSearchForm(&$form) { $form->addElement('checkbox', 'case_deleted', ts('Deleted Cases')); } - self::addCustomFormFields($form, array('Case')); + $form->addElement('text', + 'case_subject', + ts('Case Subject'), + ['class' => 'huge'] + ); + $form->addElement('text', + 'case_id', + ts('Case ID') + ); + + self::addCustomFormFields($form, ['Case']); - $form->setDefaults(array('case_owner' => 1)); + $form->setDefaults(['case_owner' => 1]); } /** @@ -731,7 +748,7 @@ public static function buildSearchForm(&$form) { * @return bool|array */ public static function formRule($fields, $files, $form) { - $errors = array(); + $errors = []; if ((empty($fields['case_from_start_date_low']) || empty($fields['case_from_start_date_high'])) && (empty($fields['case_to_end_date_low']) || empty($fields['case_to_end_date_high']))) { return TRUE; diff --git a/CRM/Case/Controller/Search.php b/CRM/Case/Controller/Search.php index 57ef1e05d884..9f3c0d133c8e 100644 --- a/CRM/Case/Controller/Search.php +++ b/CRM/Case/Controller/Search.php @@ -1,9 +1,9 @@ __table = 'civicrm_case'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'case_type_id', 'civicrm_case_type', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'case_type_id', 'civicrm_case_type', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'case_id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'case_id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case ID') , - 'description' => 'Unique Case ID', - 'required' => true, - 'import' => true, + 'title' => ts('Case ID'), + 'description' => ts('Unique Case ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_case.id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - ) , - 'case_type_id' => array( + ], + 'case_type_id' => [ 'name' => 'case_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case Type') , - 'description' => 'FK to civicrm_case_type.id', - 'required' => true, - 'import' => true, + 'title' => ts('Case Type'), + 'description' => ts('FK to civicrm_case_type.id'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_case.case_type_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => false, + 'export' => FALSE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, 'FKClassName' => 'CRM_Case_DAO_CaseType', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_case_type', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'case_subject' => array( + ], + ], + 'case_subject' => [ 'name' => 'subject', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Case Subject') , - 'description' => 'Short name of the case.', + 'title' => ts('Case Subject'), + 'description' => ts('Short name of the case.'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_case.subject', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'case_start_date' => array( + ], + ], + 'case_start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Case Start Date') , - 'description' => 'Date on which given case starts.', - 'import' => true, + 'title' => ts('Case Start Date'), + 'description' => ts('Date on which given case starts.'), + 'import' => TRUE, 'where' => 'civicrm_case.start_date', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'case_end_date' => array( + 'formatType' => 'activityDateTime', + ], + ], + 'case_end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Case End Date') , - 'description' => 'Date on which given case ends.', - 'import' => true, + 'title' => ts('Case End Date'), + 'description' => ts('Date on which given case ends.'), + 'import' => TRUE, 'where' => 'civicrm_case.end_date', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'details' => array( + 'formatType' => 'activityDateTime', + ], + ], + 'details' => [ 'name' => 'details', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Details') , - 'description' => 'Details about the meeting (agenda, notes, etc).', + 'title' => ts('Details'), + 'description' => ts('Details about the meeting (agenda, notes, etc).'), 'rows' => 8, 'cols' => 60, + 'where' => 'civicrm_case.details', 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'TextArea', - ) , - ) , - 'case_status_id' => array( + ], + ], + 'case_status_id' => [ 'name' => 'status_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case Status') , - 'description' => 'Id of case status.', - 'required' => true, - 'import' => true, + 'title' => ts('Case Status'), + 'description' => ts('Id of case status.'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_case.status_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => false, + 'export' => FALSE, 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'case_status', 'optionEditPath' => 'civicrm/admin/options/case_status', - ) - ) , - 'case_deleted' => array( + ], + ], + 'case_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Case is in the Trash') , - 'import' => true, + 'title' => ts('Case is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_case.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_case', + 'entity' => 'Case', + 'bao' => 'CRM_Case_BAO_Case', + 'localizable' => 0, + ], + 'case_created_date' => [ + 'name' => 'created_date', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Created Date'), + 'description' => ts('When was the case was created.'), + 'required' => FALSE, + 'where' => 'civicrm_case.created_date', + 'export' => TRUE, + 'default' => 'NULL', 'table_name' => 'civicrm_case', 'entity' => 'Case', 'bao' => 'CRM_Case_BAO_Case', 'localizable' => 0, - ) , - ); + ], + 'case_modified_date' => [ + 'name' => 'modified_date', + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Modified Date'), + 'description' => ts('When was the case (or closely related entity) was created or modified or deleted.'), + 'required' => FALSE, + 'where' => 'civicrm_case.modified_date', + 'export' => TRUE, + 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', + 'table_name' => 'civicrm_case', + 'entity' => 'Case', + 'bao' => 'CRM_Case_BAO_Case', + 'localizable' => 0, + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -315,10 +338,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -326,32 +350,38 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_case_type_id' => array( + $indices = [ + 'index_case_type_id' => [ 'name' => 'index_case_type_id', - 'field' => array( + 'field' => [ 0 => 'case_type_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_case::0::case_type_id', - ) , - 'index_is_deleted' => array( + ], + 'index_is_deleted' => [ 'name' => 'index_is_deleted', - 'field' => array( + 'field' => [ 0 => 'is_deleted', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_case::0::is_deleted', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Case/DAO/CaseActivity.php b/CRM/Case/DAO/CaseActivity.php index 6645ab4b2548..e608b2d6f156 100644 --- a/CRM/Case/DAO/CaseActivity.php +++ b/CRM/Case/DAO/CaseActivity.php @@ -1,171 +1,161 @@ __table = 'civicrm_case_activity'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'case_id', 'civicrm_case', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'activity_id', 'civicrm_activity', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'case_id', 'civicrm_case', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'activity_id', 'civicrm_activity', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case Activity ID') , - 'description' => 'Unique case-activity association id', - 'required' => true, + 'title' => ts('Case Activity ID'), + 'description' => ts('Unique case-activity association id'), + 'required' => TRUE, + 'where' => 'civicrm_case_activity.id', 'table_name' => 'civicrm_case_activity', 'entity' => 'CaseActivity', 'bao' => 'CRM_Case_DAO_CaseActivity', 'localizable' => 0, - ) , - 'case_id' => array( + ], + 'case_id' => [ 'name' => 'case_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case') , - 'description' => 'Case ID of case-activity association.', - 'required' => true, + 'title' => ts('Case'), + 'description' => ts('Case ID of case-activity association.'), + 'required' => TRUE, + 'where' => 'civicrm_case_activity.case_id', 'table_name' => 'civicrm_case_activity', 'entity' => 'CaseActivity', 'bao' => 'CRM_Case_DAO_CaseActivity', 'localizable' => 0, 'FKClassName' => 'CRM_Case_DAO_Case', - ) , - 'activity_id' => array( + ], + 'activity_id' => [ 'name' => 'activity_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Activity ID') , - 'description' => 'Activity ID of case-activity association.', - 'required' => true, + 'title' => ts('Activity ID'), + 'description' => ts('Activity ID of case-activity association.'), + 'required' => TRUE, + 'where' => 'civicrm_case_activity.activity_id', 'table_name' => 'civicrm_case_activity', 'entity' => 'CaseActivity', 'bao' => 'CRM_Case_DAO_CaseActivity', 'localizable' => 0, 'FKClassName' => 'CRM_Activity_DAO_Activity', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -173,10 +163,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_activity', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_activity', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -184,25 +175,31 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_activity', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_activity', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_case_activity_id' => array( + $indices = [ + 'UI_case_activity_id' => [ 'name' => 'UI_case_activity_id', - 'field' => array( + 'field' => [ 0 => 'case_id', 1 => 'activity_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_case_activity::0::case_id::activity_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Case/DAO/CaseContact.php b/CRM/Case/DAO/CaseContact.php index 8454e5b72c8d..e90afbe50c61 100644 --- a/CRM/Case/DAO/CaseContact.php +++ b/CRM/Case/DAO/CaseContact.php @@ -1,174 +1,164 @@ __table = 'civicrm_case_contact'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'case_id', 'civicrm_case', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'case_id', 'civicrm_case', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case Contact ID') , - 'description' => 'Unique case-contact association id', - 'required' => true, + 'title' => ts('Case Contact ID'), + 'description' => ts('Unique case-contact association id'), + 'required' => TRUE, + 'where' => 'civicrm_case_contact.id', 'table_name' => 'civicrm_case_contact', 'entity' => 'CaseContact', 'bao' => 'CRM_Case_BAO_CaseContact', 'localizable' => 0, - ) , - 'case_id' => array( + ], + 'case_id' => [ 'name' => 'case_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case') , - 'description' => 'Case ID of case-contact association.', - 'required' => true, + 'title' => ts('Case'), + 'description' => ts('Case ID of case-contact association.'), + 'required' => TRUE, + 'where' => 'civicrm_case_contact.case_id', 'table_name' => 'civicrm_case_contact', 'entity' => 'CaseContact', 'bao' => 'CRM_Case_BAO_CaseContact', 'localizable' => 0, 'FKClassName' => 'CRM_Case_DAO_Case', - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'Contact ID of contact record given case belongs to.', - 'required' => true, + 'title' => ts('Contact ID'), + 'description' => ts('Contact ID of contact record given case belongs to.'), + 'required' => TRUE, + 'where' => 'civicrm_case_contact.contact_id', 'table_name' => 'civicrm_case_contact', 'entity' => 'CaseContact', 'bao' => 'CRM_Case_BAO_CaseContact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => array( + 'html' => [ 'type' => 'EntityRef', - ) , - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -176,10 +166,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_contact', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_contact', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -187,26 +178,32 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_contact', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_contact', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_case_contact_id' => array( + $indices = [ + 'UI_case_contact_id' => [ 'name' => 'UI_case_contact_id', - 'field' => array( + 'field' => [ 0 => 'case_id', 1 => 'contact_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_case_contact::1::case_id::contact_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Case/DAO/CaseType.php b/CRM/Case/DAO/CaseType.php index ac84133ab94c..0fa3479e7795 100644 --- a/CRM/Case/DAO/CaseType.php +++ b/CRM/Case/DAO/CaseType.php @@ -1,242 +1,241 @@ __table = 'civicrm_case_type'; parent::__construct(); } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Case Type ID') , - 'description' => 'Autoincremented type id', - 'required' => true, + 'title' => ts('Case Type ID'), + 'description' => ts('Autoincremented type id'), + 'required' => TRUE, + 'where' => 'civicrm_case_type.id', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - 'name' => array( + ], + 'name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Case Type Name') , - 'description' => 'Machine name for Case Type', - 'required' => true, + 'title' => ts('Case Type Name'), + 'description' => ts('Machine name for Case Type'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_case_type.name', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - 'title' => array( + ], + 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Case Type Title') , - 'description' => 'Natural language name for Case Type', - 'required' => true, + 'title' => ts('Case Type Title'), + 'description' => ts('Natural language name for Case Type'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_case_type.title', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 1, - ) , - 'description' => array( + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Case Type Description') , - 'description' => 'Description of the Case Type', + 'title' => ts('Case Type Description'), + 'description' => ts('Description of the Case Type'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_case_type.description', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 1, - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Case Type Is Active') , - 'description' => 'Is this entry active?', + 'title' => ts('Case Type Is Active'), + 'description' => ts('Is this entry active?'), + 'where' => 'civicrm_case_type.is_active', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - 'is_reserved' => array( + ], + 'is_reserved' => [ 'name' => 'is_reserved', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Case Type Is Reserved') , - 'description' => 'Is this case type a predefined system type?', + 'title' => ts('Case Type Is Reserved'), + 'description' => ts('Is this case type a predefined system type?'), + 'where' => 'civicrm_case_type.is_reserved', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - 'weight' => array( + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Order') , - 'description' => 'Ordering of the case types', - 'required' => true, + 'title' => ts('Order'), + 'description' => ts('Ordering of the case types'), + 'required' => TRUE, + 'where' => 'civicrm_case_type.weight', 'default' => '1', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - 'definition' => array( + ], + 'definition' => [ 'name' => 'definition', 'type' => CRM_Utils_Type::T_BLOB, - 'title' => ts('Case Type Definition') , - 'description' => 'xml definition of case type', + 'title' => ts('Case Type Definition'), + 'description' => ts('xml definition of case type'), + 'where' => 'civicrm_case_type.definition', 'table_name' => 'civicrm_case_type', 'entity' => 'CaseType', 'bao' => 'CRM_Case_BAO_CaseType', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -244,10 +243,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_type', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'case_type', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -255,25 +255,31 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_type', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'case_type', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'case_type_name' => array( + $indices = [ + 'case_type_name' => [ 'name' => 'case_type_name', - 'field' => array( + 'field' => [ 0 => 'name', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_case_type::1::name', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Case/Form/Activity.php b/CRM/Case/Form/Activity.php index 338cd364572d..f2927e11a696 100644 --- a/CRM/Case/Form/Activity.php +++ b/CRM/Case/Form/Activity.php @@ -1,9 +1,9 @@ _caseId = explode(',', $caseIds); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $caseIds = CRM_Utils_Request::retrieve('caseid', 'CommaSeparatedIntegers', $this); + $this->_caseId = $caseIds ? explode(',', $caseIds) : []; + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); if (!$this->_context) { $this->_context = 'caseActivity'; } $this->_crmDir = 'Case'; $this->assign('context', $this->_context); - $result = parent::preProcess(); + parent::preProcess(); - $scheduleStatusId = CRM_Core_OptionGroup::getValue('activity_status', 'Scheduled', 'name'); + $scheduleStatusId = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Scheduled'); $this->assign('scheduleStatusId', $scheduleStatusId); if (!$this->_caseId && $this->_activityId) { - $this->_caseId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseActivity', $this->_activityId, + $this->_caseId = (array) CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseActivity', $this->_activityId, 'case_id', 'activity_id' ); } @@ -100,8 +108,8 @@ public function preProcess() { if ($this->_caseId && !CRM_Core_Permission::check('access all cases and activities') ) { - $session = CRM_Core_Session::singleton(); - $allCases = CRM_Case_BAO_Case::getCases(TRUE, $session->get('userID'), 'any'); + $params = ['type' => 'any']; + $allCases = CRM_Case_BAO_Case::getCases(TRUE, $params); if (count(array_intersect($this->_caseId, array_keys($allCases))) == 0) { CRM_Core_Error::fatal(ts('You are not authorized to access this page.')); } @@ -124,6 +132,8 @@ public function preProcess() { } $this->assign('caseType', $this->_caseType); + $this->_caseTypeDefinition = $this->getCaseTypeDefinition(); + $xmlProcessorProcess = new CRM_Case_XMLProcessor_Process(); $isMultiClient = $xmlProcessorProcess->getAllowMultipleCaseClients(); $this->assign('multiClient', $isMultiClient); @@ -168,35 +178,30 @@ public function preProcess() { $caseType = $this->_caseType[$casePos]; $activityInst = $xmlProcessor->getMaxInstance($caseType); - // If not bounce back and also provide activity edit link + // If not bounce back and also provide activity edit link if only one existing activity if (isset($activityInst[$this->_activityTypeName])) { $activityCount = CRM_Case_BAO_Case::getCaseActivityCount($caseId, $this->_activityTypeId); - if ($activityCount >= $activityInst[$this->_activityTypeName]) { - if ($activityInst[$this->_activityTypeName] == 1) { - $atArray = array('activity_type_id' => $this->_activityTypeId); - $activities = CRM_Case_BAO_Case::getCaseActivity($caseId, - $atArray, - $this->_currentUserId - ); - $activities = array_keys($activities); - $activities = $activities[0]; - $editUrl = CRM_Utils_System::url('civicrm/case/activity', - "reset=1&cid={$this->_currentlyViewedContactId}&caseid={$caseId}&action=update&id={$activities}" - ); - } - CRM_Core_Error::statusBounce(ts("You can not add another '%1' activity to this case. %2", - array( - 1 => $this->_activityTypeName, - 2 => ts("Do you want to edit the existing activity?", array(1 => "href='$editUrl'")), - ) - ), - $url - ); + $editUrl = self::checkMaxInstances( + $caseId, + $this->_activityTypeId, + $activityInst[$this->_activityTypeName], + $this->_currentUserId, + $this->_currentlyViewedContactId, + $activityCount + ); + $bounceMessage = self::getMaxInstancesBounceMessage($editUrl, $this->_activityTypeName, $activityInst[$this->_activityTypeName], $activityCount); + if ($bounceMessage) { + CRM_Core_Error::statusBounce($bounceMessage, $url); } } } } + // Turn off the prompt which asks the user if they want to create separate + // activities when specifying multiple contacts "with" a new activity. + // Instead, always create one activity with all contacts together. + $this->supportsActivitySeparation = FALSE; + $session = CRM_Core_Session::singleton(); $session->pushUserContext($url); } @@ -206,7 +211,7 @@ public function preProcess() { */ public function setDefaultValues() { $this->_defaults = parent::setDefaultValues(); - $targetContactValues = array(); + $targetContactValues = []; foreach ($this->_caseId as $key => $val) { //get all clients. $clients = CRM_Case_BAO_Case::getContactNames($val); @@ -244,19 +249,40 @@ public function buildQuickForm() { $this->_fields['source_contact_id']['label'] = ts('Reported By'); unset($this->_fields['status_id']['attributes']['required']); + if ($this->restrictAssignmentByUserAccount()) { + $assigneeParameters['uf_user'] = 1; + } + + $activityAssignmentGroups = $this->getActivityAssignmentGroups(); + if (!empty($activityAssignmentGroups)) { + $assigneeParameters['group'] = ['IN' => $activityAssignmentGroups]; + } + + if (!empty($assigneeParameters)) { + $this->_fields['assignee_contact_id']['attributes']['api']['params'] + = array_merge($this->_fields['assignee_contact_id']['attributes']['api']['params'], $assigneeParameters); + + $this->_fields['followup_assignee_contact_id']['attributes']['api']['params'] + = array_merge($this->_fields['followup_assignee_contact_id']['attributes']['api']['params'], $assigneeParameters); + + //Disallow creating a contact from the assignee field UI. + $this->_fields['assignee_contact_id']['attributes']['create'] = FALSE; + $this->_fields['followup_assignee_contact_id']['attributes']['create'] = FALSE; + } + if ($this->_caseType) { $xmlProcessor = new CRM_Case_XMLProcessor_Process(); - $aTypes = array(); - foreach ($this->_caseType as $key => $val) { + $aTypes = []; + foreach (array_unique($this->_caseType) as $val) { $activityTypes = $xmlProcessor->get($val, 'ActivityTypes', TRUE); $aTypes = $aTypes + $activityTypes; } // remove Open Case activity type since we're inside an existing case - $openCaseID = CRM_Core_OptionGroup::getValue('activity_type', 'Open Case', 'name'); + $openCaseID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case'); unset($aTypes[$openCaseID]); asort($aTypes); - $this->_fields['followup_activity_type_id']['attributes'] = array('' => '- select activity type -') + $aTypes; + $this->_fields['followup_activity_type_id']['attributes'] = ['' => '- select activity type -'] + $aTypes; } parent::buildQuickForm(); @@ -268,17 +294,17 @@ public function buildQuickForm() { $this->assign('urlPath', 'civicrm/case/activity'); $encounterMediums = CRM_Case_PseudoConstant::encounterMedium(); - // Fixme: what's the justification for this? It seems like it is just re-adding an option in case it is the default and disabled. - // Is that really a big problem? - if ($this->_activityTypeFile == 'OpenCase') { - $this->_encounterMedium = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $this->_activityId, - 'medium_id' - ); - if (!array_key_exists($this->_encounterMedium, $encounterMediums)) { - $encounterMediums[$this->_encounterMedium] = CRM_Core_OptionGroup::getLabel('encounter_medium', - $this->_encounterMedium, - FALSE - ); + + if ($this->_activityTypeFile == 'OpenCase' && $this->_action == CRM_Core_Action::UPDATE) { + $this->getElement('activity_date_time')->freeze(); + + if ($this->_activityId) { + // Fixme: what's the justification for this? It seems like it is just re-adding an option in case it is the default and disabled. + // Is that really a big problem? + $this->_encounterMedium = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $this->_activityId, 'medium_id'); + if (!array_key_exists($this->_encounterMedium, $encounterMediums)) { + $encounterMediums[$this->_encounterMedium] = CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'medium_id', $this->_encounterMedium); + } } } @@ -305,22 +331,22 @@ public function buildQuickForm() { } if (!empty($this->_relatedContacts)) { - $checkBoxes = array(); + $checkBoxes = []; foreach ($this->_relatedContacts as $id => $row) { foreach ($row as $key => $value) { - $checkBoxes[$key] = $this->addElement('checkbox', $key, NULL, NULL, array('class' => 'select-row')); + $checkBoxes[$key] = $this->addElement('checkbox', $key, NULL, NULL, ['class' => 'select-row']); } } $this->addGroup($checkBoxes, 'contact_check'); $this->addElement('checkbox', 'toggleSelect', NULL, NULL, - array('class' => 'select-rows') + ['class' => 'select-rows'] ); $this->assign('searchRows', $this->_relatedContacts); } $this->_relatedContacts = $rgc + $relCon; - $this->addFormRule(array('CRM_Case_Form_Activity', 'formRule'), $this); + $this->addFormRule(['CRM_Case_Form_Activity', 'formRule'], $this); } /** @@ -361,7 +387,7 @@ public function postProcess($params = NULL) { $caseAttributeActivities = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, $activityCondition); if (!array_key_exists($this->_activityTypeId, $caseAttributeActivities)) { - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; $activityDelete = CRM_Activity_BAO_Activity::deleteActivity($params, TRUE); if ($activityDelete) { $statusMsg = ts('The selected activity has been moved to the Trash. You can view and / or restore deleted activities by checking "Deleted Activities" from the Case Activities search filter (under Manage Case).
    '); @@ -371,10 +397,10 @@ public function postProcess($params = NULL) { $statusMsg = ts("Selected Activity cannot be deleted."); } - $tagParams = array( + $tagParams = [ 'entity_table' => 'civicrm_activity', 'entity_id' => $this->_activityId, - ); + ]; CRM_Core_BAO_EntityTag::del($tagParams); CRM_Core_Session::setStatus('', $statusMsg, 'info'); @@ -383,7 +409,7 @@ public function postProcess($params = NULL) { if ($this->_action & CRM_Core_Action::RENEW) { $statusMsg = NULL; - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; $activityRestore = CRM_Activity_BAO_Activity::restoreActivity($params); if ($activityRestore) { $statusMsg = ts('The selected activity has been restored.
    '); @@ -393,15 +419,16 @@ public function postProcess($params = NULL) { } // store the submitted values in an array - $params = $this->controller->exportValues($this->_name); + // Explanation for why we only check the is_unittest element: Prior to adding that check, there was no check and so any $params passed in would have been overwritten. Just in case somebody is passing in some non-null params and that broken code would have inadvertently been working, we can maintain backwards compatibility by only checking for the is_unittest parameter, and so that broken code will still work. At the same time this allows unit tests to pass in a $params without it getting overwritten. See also PR #2077 for some discussion of when the $params parameter was added as a passed in variable. + if (empty($params['is_unittest'])) { + $params = $this->controller->exportValues($this->_name); + } //set parent id if its edit mode if ($parentId = CRM_Utils_Array::value('parent_id', $this->_defaults)) { $params['parent_id'] = $parentId; } - // store the dates with proper format - $params['activity_date_time'] = CRM_Utils_Date::processDate($params['activity_date_time'], $params['activity_date_time_time']); $params['activity_type_id'] = $this->_activityTypeId; // format with contact (target contact) values @@ -409,20 +436,20 @@ public function postProcess($params = NULL) { $params['target_contact_id'] = explode(',', $params['target_contact_id']); } else { - $params['target_contact_id'] = array(); + $params['target_contact_id'] = []; } // format activity custom data if (!empty($params['hidden_custom'])) { if ($this->_activityId) { // retrieve and include the custom data of old Activity - $oldActivity = civicrm_api3('Activity', 'getsingle', array('id' => $this->_activityId)); + $oldActivity = civicrm_api3('Activity', 'getsingle', ['id' => $this->_activityId]); $params = array_merge($oldActivity, $params); // unset custom fields-id from params since we want custom // fields to be saved for new activity. foreach ($params as $key => $value) { - $match = array(); + $match = []; if (preg_match('/^(custom_\d+_)(\d+)$/', $key, $match)) { $params[$match[1] . '-1'] = $params[$key]; @@ -454,7 +481,7 @@ public function postProcess($params = NULL) { $params['assignee_contact_id'] = explode(',', $params['assignee_contact_id']); } else { - $params['assignee_contact_id'] = array(); + $params['assignee_contact_id'] = []; } if (isset($this->_activityId)) { @@ -485,7 +512,7 @@ public function postProcess($params = NULL) { $params['case_id'] = $val; // activity create/update $activity = CRM_Activity_BAO_Activity::create($params); - $vvalue[] = array('case_id' => $val, 'actId' => $activity->id); + $vvalue[] = ['case_id' => $val, 'actId' => $activity->id]; // call end post process, after the activity has been created/updated. $this->endPostProcess($params, $activity); } @@ -493,7 +520,7 @@ public function postProcess($params = NULL) { else { // since the params we need to set are very few, and we don't want rest of the // work done by bao create method , lets use dao object to make the changes - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; $params['is_current_revision'] = 0; $activity = new CRM_Activity_DAO_Activity(); $activity->copyValues($params); @@ -523,13 +550,13 @@ public function postProcess($params = NULL) { foreach ($this->_caseId as $key => $val) { $newActParams['case_id'] = $val; $activity = CRM_Activity_BAO_Activity::create($newActParams); - $vvalue[] = array('case_id' => $val, 'actId' => $activity->id); + $vvalue[] = ['case_id' => $val, 'actId' => $activity->id]; // call end post process, after the activity has been created/updated. $this->endPostProcess($newActParams, $activity); } // copy files attached to old activity if any, to new one, // as long as users have not selected the 'delete attachment' option. - if (empty($newActParams['is_delete_attachment'])) { + if (empty($newActParams['is_delete_attachment']) && ($this->_activityId != $activity->id)) { CRM_Core_BAO_File::copyEntityFile('civicrm_activity', $this->_activityId, 'civicrm_activity', $activity->id ); @@ -542,11 +569,13 @@ public function postProcess($params = NULL) { foreach ($vvalue as $vkey => $vval) { if ($vval['actId']) { // add tags if exists - $tagParams = array(); + $tagParams = []; if (!empty($params['tag'])) { - foreach ($params['tag'] as $tag) { - $tagParams[$tag] = 1; + if (!is_array($params['tag'])) { + $params['tag'] = explode(',', $params['tag']); } + + $tagParams = array_fill_keys($params['tag'], 1); } //save static tags @@ -569,28 +598,22 @@ public function postProcess($params = NULL) { unset($caseParams['subject'], $caseParams['details'], $caseParams['status_id'], $caseParams['custom'] ); - $case = CRM_Case_BAO_Case::create($caseParams); + CRM_Case_BAO_Case::create($caseParams); // create case activity record - $caseParams = array( + $caseParams = [ 'activity_id' => $vval['actId'], 'case_id' => $vval['case_id'], - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); } - // Insert civicrm_log record for the activity (e.g. store the - // created / edited by contact id and date for the activity) - // Note - civicrm_log is already created by CRM_Activity_BAO_Activity::create() - // send copy to selected contacts. $mailStatus = ''; - $mailToContacts = array(); + $mailToContacts = []; //CRM-5695 //check for notification settings for assignee contacts - $selectedContacts = array('contact_check'); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); - $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); + $selectedContacts = ['contact_check']; if (Civi::settings()->get('activity_assignee_notification')) { $selectedContacts[] = 'assignee_contact_id'; } @@ -602,7 +625,7 @@ public function postProcess($params = NULL) { $mailStatus = ts("A copy of the activity has also been sent to selected contacts(s)."); } else { - $this->_relatedContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames(array($vval['actId']), TRUE, FALSE); + $this->_relatedContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames([$vval['actId']], TRUE, FALSE); $mailStatus .= ' ' . ts("A copy of the activity has also been sent to assignee contacts(s)."); } //build an associative array with unique email addresses. @@ -628,7 +651,7 @@ public function postProcess($params = NULL) { } } - $extraParams = array('case_id' => $vval['case_id'], 'client_id' => $this->_currentlyViewedContactId); + $extraParams = ['case_id' => $vval['case_id'], 'client_id' => $this->_currentlyViewedContactId]; $result = CRM_Activity_BAO_Activity::sendToAssignee($activity, $mailToContacts, $extraParams); if (empty($result)) { $mailStatus = ''; @@ -640,17 +663,166 @@ public function postProcess($params = NULL) { $followupActivity = CRM_Activity_BAO_Activity::createFollowupActivity($vval['actId'], $params); if ($followupActivity) { - $caseParams = array( + $caseParams = [ 'activity_id' => $followupActivity->id, 'case_id' => $vval['case_id'], - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); $followupStatus = ts("A followup activity has been scheduled.") . '

    '; } } - $title = ts("%1 Saved", array(1 => $this->_activityTypeName)); + $title = ts("%1 Saved", [1 => $this->_activityTypeName]); CRM_Core_Session::setStatus($followupStatus . $mailStatus, $title, 'success'); } } + /** + * Returns the groups that contacts must belong to in order to be assigned + * an activity for this case. It returns an empty array if no groups are found for + * the case type linked to the caseId. + * + * @return array + */ + private function getActivityAssignmentGroups() { + if (!$this->_caseTypeDefinition) { + return []; + } + + $assignmentGroups = []; + foreach ($this->_caseTypeDefinition as $caseId => $definition) { + if (!empty($definition['activityAsgmtGrps'])) { + $assignmentGroups = array_merge($assignmentGroups, $definition['activityAsgmtGrps']); + } + } + + return $assignmentGroups; + } + + /** + * Returns whether contacts must have a user account in order to be + * assigned an activity for this case. + * + * @return bool + */ + private function restrictAssignmentByUserAccount() { + if (!$this->_caseTypeDefinition) { + return FALSE; + } + + foreach ($this->_caseTypeDefinition as $caseId => $definition) { + if (!empty($definition['restrictActivityAsgmtToCmsUser'])) { + return TRUE; + } + } + + return FALSE; + } + + /** + * Returns the case type definition column value for the case type linked to the caseId. + * + * @return array + */ + private function getCaseTypeDefinition() { + if (!$this->_caseId) { + return []; + } + + $definitions = civicrm_api3('CaseType', 'get', [ + 'return' => ['name', 'definition'], + 'name' => ['IN' => array_unique($this->_caseType)], + ]); + + return array_column($definitions['values'], 'definition', 'name'); + } + + /** + * Get the edit link for a case activity + * + * This isn't here for reusability - it was a pull out + * from preProcess to make it easier to test. + * There is CRM_Case_Selector_Search::addCaseActivityLinks but it would + * need some rejigging, and there's also a FIXME note there already. + * + * @param int $caseId + * @param int $activityTypeId + * @param int $currentUserId + * @param int $currentlyViewedContactId + * + * @return string + */ + public static function getCaseActivityEditLink($caseId, $activityTypeId, $currentUserId, $currentlyViewedContactId) { + $atArray = ['activity_type_id' => $activityTypeId]; + $activities = CRM_Case_BAO_Case::getCaseActivity($caseId, + $atArray, + $currentUserId + ); + $firstActivity = CRM_Utils_Array::first($activities['data']); + $activityId = empty($firstActivity['DT_RowId']) ? 0 : $firstActivity['DT_RowId']; + return CRM_Utils_System::url('civicrm/case/activity', + "reset=1&cid={$currentlyViewedContactId}&caseid={$caseId}&action=update&id={$activityId}" + ); + } + + /** + * Check the current activity count against max instances for a given case id and activity type. + * + * This isn't here for reusability - it was a pull out + * from preProcess to make it easier to test. + * + * @param int $caseId + * @param int $activityTypeId + * @param int $maxInstances + * @param int $currentUserId + * @param int $currentlyViewedContactId + * @param int $activityCount + * + * @return string + * If there is more than one existing activity of the given type then it's not clear which url to return so return null for the url. + */ + public static function checkMaxInstances($caseId, $activityTypeId, $maxInstances, $currentUserId, $currentlyViewedContactId, $activityCount) { + $editUrl = NULL; + if ($activityCount >= $maxInstances) { + if ($maxInstances == 1) { + $editUrl = self::getCaseActivityEditLink($caseId, $activityTypeId, $currentUserId, $currentlyViewedContactId); + } + } + return $editUrl; + } + + /** + * Compute the message text for the bounce message when max_instances is reached, depending on whether it's one or more than one. + * + * @param string $editUrl + * @param string $activityTypeName + * This is actually label!! But we do want label though in this function. + * @param int $maxInstances + * @param int $activityCount + * Count of existing activities of the given type on the case + * + * @return string + */ + public static function getMaxInstancesBounceMessage($editUrl, $activityTypeName, $maxInstances, $activityCount) { + $bounceMessage = NULL; + if ($activityCount >= $maxInstances) { + if ($maxInstances == 1) { + $bounceMessage = ts("You can not add another '%1' activity to this case. %2", + [ + 1 => $activityTypeName, + 2 => ts("Do you want to edit the existing activity?", [1 => "href='$editUrl'"]), + ] + ); + } + else { + // More than one instance, so don't provide a link. What would it be a link to anyway? + $bounceMessage = ts("You can not add another '%1' activity to this case.", + [ + 1 => $activityTypeName, + ] + ); + } + } + return $bounceMessage; + } + } diff --git a/CRM/Case/Form/Activity/ChangeCaseStartDate.php b/CRM/Case/Form/Activity/ChangeCaseStartDate.php index 8e2eead76a32..aecb3caef7e8 100644 --- a/CRM/Case/Form/Activity/ChangeCaseStartDate.php +++ b/CRM/Case/Form/Activity/ChangeCaseStartDate.php @@ -1,9 +1,9 @@ _caseId); - $openCaseParams = array('activity_type_id' => $openCaseActivityType); + $openCaseParams = ['activity_type_id' => $openCaseActivityType]; $openCaseInfo = CRM_Case_BAO_Case::getCaseActivityDates($caseId, $openCaseParams, TRUE); if (empty($openCaseInfo)) { - list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults(); + $defaults['start_date'] = date('Y-m-d H:i:s'); } else { // We know there can only be one result @@ -79,7 +76,7 @@ public static function setDefaultValues(&$form) { // store activity id for updating it later $form->openCaseActivityId = $openCaseInfo['id']; - list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($openCaseInfo['activity_date'], 'activityDateTime'); + $defaults['start_date'] = $openCaseInfo['activity_date']; } return $defaults; } @@ -94,7 +91,7 @@ public static function buildQuickForm(&$form) { $currentStartDate = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseId, 'start_date'); $form->assign('current_start_date', $currentStartDate); - $form->addDate('start_date', ts('New Start Date'), FALSE, array('formatType' => 'activityDateTime')); + $form->add('datepicker', 'start_date', ts('New Start Date'), [], TRUE); } /** @@ -135,10 +132,6 @@ public static function beginPostProcess(&$form, &$params) { * @param $activity */ public static function endPostProcess(&$form, &$params, $activity) { - if (!empty($params['start_date'])) { - $params['start_date'] = CRM_Utils_Date::processDate($params['start_date'], $params['start_date_time']); - } - $caseType = CRM_Utils_Array::first($form->_caseType); $caseId = CRM_Utils_Array::first($form->_caseId); @@ -156,22 +149,22 @@ public static function endPostProcess(&$form, &$params, $activity) { $config = CRM_Core_Config::singleton(); - $params['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $activity->status_id = $params['status_id']; - $params['priority_id'] = CRM_Core_OptionGroup::getValue('priority', 'Normal', 'name'); + $params['priority_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Normal'); $activity->priority_id = $params['priority_id']; // 1. save activity subject with new start date $currentStartDate = CRM_Utils_Date::customFormat(CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseId, 'start_date' ), $config->dateformatFull); - $newStartDate = CRM_Utils_Date::customFormat(CRM_Utils_Date::mysqlToIso($params['start_date']), $config->dateformatFull); + $newStartDate = CRM_Utils_Date::customFormat($params['start_date'], $config->dateformatFull); $subject = 'Change Case Start Date from ' . $currentStartDate . ' to ' . $newStartDate; $activity->subject = $subject; $activity->save(); // 2. initiate xml processor $xmlProcessor = new CRM_Case_XMLProcessor_Process(); - $xmlProcessorParams = array( + $xmlProcessorParams = [ 'clientID' => $form->_currentlyViewedContactId, 'creatorID' => $form->_currentUserId, 'standardTimeline' => 0, @@ -181,7 +174,7 @@ public static function endPostProcess(&$form, &$params, $activity) { 'activityTypeName' => 'Change Case Start Date', 'activitySetName' => 'standard_timeline', 'resetTimeline' => 1, - ); + ]; $xmlProcessor->run($caseType, $xmlProcessorParams); @@ -190,21 +183,21 @@ public static function endPostProcess(&$form, &$params, $activity) { if ($form->openCaseActivityId) { $abao = new CRM_Activity_BAO_Activity(); - $oldParams = array('id' => $form->openCaseActivityId); - $oldActivityDefaults = array(); + $oldParams = ['id' => $form->openCaseActivityId]; + $oldActivityDefaults = []; $oldActivity = $abao->retrieve($oldParams, $oldActivityDefaults); // save the old values require_once 'api/v3/utils.php'; - $openCaseParams = array(); + $openCaseParams = []; //@todo calling api functions directly is not supported _civicrm_api3_object_to_array($oldActivity, $openCaseParams); // update existing revision - $oldParams = array( + $oldParams = [ 'id' => $form->openCaseActivityId, 'is_current_revision' => 0, - ); + ]; $oldActivity = new CRM_Activity_DAO_Activity(); $oldActivity->copyValues($oldParams); $oldActivity->save(); @@ -226,17 +219,17 @@ public static function endPostProcess(&$form, &$params, $activity) { } else { // Create linkage to case - $caseActivityParams = array( + $caseActivityParams = [ 'activity_id' => $newActivity->id, 'case_id' => $caseId, - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseActivityParams); - $caseActivityParams = array( + $caseActivityParams = [ 'activityID' => $form->openCaseActivityId, 'mainActivityId' => $newActivity->id, - ); + ]; CRM_Activity_BAO_Activity::copyExtendedActivityData($caseActivityParams); } } diff --git a/CRM/Case/Form/Activity/ChangeCaseStatus.php b/CRM/Case/Form/Activity/ChangeCaseStatus.php index 9cf971ea7cc8..aa4a6ce43fd4 100644 --- a/CRM/Case/Form/Activity/ChangeCaseStatus.php +++ b/CRM/Case/Form/Activity/ChangeCaseStatus.php @@ -1,9 +1,9 @@ _caseId)) { CRM_Core_Error::fatal(ts('Case Id not found.')); } + + $form->addElement('checkbox', 'updateLinkedCases', NULL, NULL, ['class' => 'select-row']); + + $caseID = CRM_Utils_Array::first($form->_caseId); + $cases = CRM_Case_BAO_Case::getRelatedCases($caseID); + $form->assign('linkedCases', $cases); } /** @@ -58,7 +64,7 @@ public static function preProcess(&$form) { * @return array */ public static function setDefaultValues(&$form) { - $defaults = array(); + $defaults = []; // Retrieve current case status $defaults['case_status_id'] = $form->_defaultCaseStatus; @@ -72,17 +78,17 @@ public static function buildQuickForm(&$form) { $form->removeElement('status_id'); $form->removeElement('priority_id'); - $caseTypes = array(); + $caseTypes = []; $form->_caseStatus = CRM_Case_PseudoConstant::caseStatus(); $statusNames = CRM_Case_PseudoConstant::caseStatus('name'); // Limit case statuses to allowed types for these case(s) - $allCases = civicrm_api3('Case', 'get', array('return' => 'case_type_id', 'id' => array('IN' => (array) $form->_caseId))); + $allCases = civicrm_api3('Case', 'get', ['return' => 'case_type_id', 'id' => ['IN' => (array) $form->_caseId]]); foreach ($allCases['values'] as $case) { $caseTypes[$case['case_type_id']] = $case['case_type_id']; } - $caseTypes = civicrm_api3('CaseType', 'get', array('id' => array('IN' => $caseTypes))); + $caseTypes = civicrm_api3('CaseType', 'get', ['id' => ['IN' => $caseTypes]]); foreach ($caseTypes['values'] as $ct) { if (!empty($ct['definition']['statuses'])) { foreach ($form->_caseStatus as $id => $label) { @@ -99,10 +105,7 @@ public static function buildQuickForm(&$form) { foreach ($form->_defaultCaseStatus as $keydefault => $valdefault) { if (!array_key_exists($valdefault, $form->_caseStatus)) { - $form->_caseStatus[$valdefault] = CRM_Core_OptionGroup::getLabel('case_status', - $valdefault, - FALSE - ); + $form->_caseStatus[$valdefault] = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'status_id', $valdefault); } } $element = $form->add('select', 'case_status_id', ts('Case Status'), @@ -136,18 +139,27 @@ public static function formRule($values, $files, $form) { /** * Process the form submission. * - * * @param CRM_Core_Form $form * @param array $params */ public static function beginPostProcess(&$form, &$params) { $params['id'] = CRM_Utils_Array::value('case_id', $params); + + if (CRM_Utils_Array::value('updateLinkedCases', $params) === '1') { + $caseID = CRM_Utils_Array::first($form->_caseId); + $cases = CRM_Case_BAO_Case::getRelatedCases($caseID); + + foreach ($cases as $currentCase) { + if ($currentCase['status_id'] != $params['case_status_id']) { + $form->_caseId[] = $currentCase['case_id']; + } + } + } } /** * Process the form submission. * - * * @param CRM_Core_Form $form * @param array $params * @param CRM_Activity_BAO_Activity $activity @@ -157,19 +169,19 @@ public static function endPostProcess(&$form, &$params, $activity) { // Set case end_date if we're closing the case. Clear end_date if we're (re)opening it. if (CRM_Utils_Array::value($params['case_status_id'], $groupingValues) == 'Closed' && !empty($params['activity_date_time'])) { - $params['end_date'] = $params['activity_date_time']; + $params['end_date'] = CRM_Utils_Date::isoToMysql($params['activity_date_time']); // End case-specific relationships (roles) foreach ($params['target_contact_id'] as $cid) { $rels = CRM_Case_BAO_Case::getCaseRoles($cid, $params['case_id']); - // FIXME: Is there an existing function to close a relationship? - $query = 'UPDATE civicrm_relationship SET end_date=%2 WHERE id=%1'; foreach ($rels as $relId => $relData) { - $relParams = array( - 1 => array($relId, 'Integer'), - 2 => array($params['end_date'], 'Timestamp'), - ); - CRM_Core_DAO::executeQuery($query, $relParams); + $relationshipParams = [ + 'id' => $relId, + 'end_date' => $params['end_date'], + ]; + // @todo we can't switch directly to api because there is too much business logic and it breaks closing cases with organisations as client relationships + //civicrm_api3('Relationship', 'create', $relationshipParams); + CRM_Contact_BAO_Relationship::add($relationshipParams); } } } @@ -178,27 +190,29 @@ public static function endPostProcess(&$form, &$params, $activity) { // Reopen case-specific relationships (roles) foreach ($params['target_contact_id'] as $cid) { - $rels = CRM_Case_BAO_Case::getCaseRoles($cid, $params['case_id']); - // FIXME: Is there an existing function? - $query = 'UPDATE civicrm_relationship SET end_date=NULL WHERE id=%1'; + $rels = CRM_Case_BAO_Case::getCaseRoles($cid, $params['case_id'], NULL, FALSE); foreach ($rels as $relId => $relData) { - $relParams = array(1 => array($relId, 'Integer')); - CRM_Core_DAO::executeQuery($query, $relParams); + $relationshipParams = [ + 'id' => $relId, + 'end_date' => 'null', + ]; + // @todo we can't switch directly to api because there is too much business logic and it breaks closing cases with organisations as client relationships + //civicrm_api3('Relationship', 'create', $relationshipParams); + CRM_Contact_BAO_Relationship::add($relationshipParams); } } } - $params['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $activity->status_id = $params['status_id']; - $params['priority_id'] = CRM_Core_OptionGroup::getValue('priority', 'Normal', 'name'); + $params['priority_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Normal'); $activity->priority_id = $params['priority_id']; foreach ($form->_oldCaseStatus as $statuskey => $statusval) { if ($activity->subject == 'null') { - $activity->subject = ts('Case status changed from %1 to %2', array( - 1 => CRM_Utils_Array::value($statusval, $form->_caseStatus), - 2 => CRM_Utils_Array::value($params['case_status_id'], $form->_caseStatus), - ) - ); + $activity->subject = ts('Case status changed from %1 to %2', [ + 1 => CRM_Utils_Array::value($statusval, $form->_caseStatus), + 2 => CRM_Utils_Array::value($params['case_status_id'], $form->_caseStatus), + ]); $activity->save(); } } diff --git a/CRM/Case/Form/Activity/ChangeCaseType.php b/CRM/Case/Form/Activity/ChangeCaseType.php index ae3894afba49..1e683a8beb71 100644 --- a/CRM/Case/Form/Activity/ChangeCaseType.php +++ b/CRM/Case/Form/Activity/ChangeCaseType.php @@ -1,9 +1,9 @@ _caseTypeId; return $defaults; @@ -85,11 +84,11 @@ public static function buildQuickForm(&$form) { $form->_caseType[$form->_caseTypeId] = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseType', $form->_caseTypeId, 'title'); } - $form->addField('case_type_id', array('context' => 'create', 'entity' => 'Case')); + $form->addField('case_type_id', ['context' => 'create', 'entity' => 'Case']); // timeline - $form->addYesNo('is_reset_timeline', ts('Reset Case Timeline?'), NULL, TRUE, array('onclick' => "return showHideByValue('is_reset_timeline','','resetTimeline','table-row','radio',false);")); - $form->addDateTime('reset_date_time', ts('Reset Start Date'), FALSE, array('formatType' => 'activityDateTime')); + $form->addYesNo('is_reset_timeline', ts('Reset Case Timeline?'), NULL, TRUE); + $form->add('datepicker', 'reset_date_time', ts('Reset Start Date'), NULL, FALSE, ['allowClear' => FALSE]); } /** @@ -120,13 +119,9 @@ public static function beginPostProcess(&$form, &$params) { $params['id'] = $form->_id; } - if (CRM_Utils_Array::value('is_reset_timeline', $params) == 0) { + if (empty($params['is_reset_timeline'])) { unset($params['reset_date_time']); } - else { - // store the date with proper format - $params['reset_date_time'] = CRM_Utils_Date::processDate($params['reset_date_time'], $params['reset_date_time_time']); - } } /** @@ -158,17 +153,17 @@ public static function endPostProcess(&$form, &$params, $activity) { CRM_Core_Error::fatal('Required parameter missing for ChangeCaseType - end post processing'); } - $params['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); + $params['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $activity->status_id = $params['status_id']; - $params['priority_id'] = CRM_Core_OptionGroup::getValue('priority', 'Normal', 'name'); + $params['priority_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'priority_id', 'Normal'); $activity->priority_id = $params['priority_id']; if ($activity->subject == 'null') { $activity->subject = ts('Case type changed from %1 to %2', - array( + [ 1 => CRM_Utils_Array::value($form->_defaults['case_type_id'], $allCaseTypes), 2 => CRM_Utils_Array::value($params['case_type_id'], $allCaseTypes), - ) + ] ); $activity->save(); } @@ -176,7 +171,7 @@ public static function endPostProcess(&$form, &$params, $activity) { // 1. initiate xml processor $xmlProcessor = new CRM_Case_XMLProcessor_Process(); $caseId = CRM_Utils_Array::first($form->_caseId); - $xmlProcessorParams = array( + $xmlProcessorParams = [ 'clientID' => $form->_currentlyViewedContactId, 'creatorID' => $form->_currentUserId, 'standardTimeline' => 1, @@ -184,7 +179,7 @@ public static function endPostProcess(&$form, &$params, $activity) { 'activity_date_time' => CRM_Utils_Array::value('reset_date_time', $params), 'caseID' => $caseId, 'resetTimeline' => CRM_Utils_Array::value('is_reset_timeline', $params), - ); + ]; $xmlProcessor->run($caseType, $xmlProcessorParams); // status msg diff --git a/CRM/Case/Form/Activity/LinkCases.php b/CRM/Case/Form/Activity/LinkCases.php index dc77beb0cf2b..322225c52b39 100644 --- a/CRM/Case/Form/Activity/LinkCases.php +++ b/CRM/Case/Form/Activity/LinkCases.php @@ -1,9 +1,9 @@ get('relatedCases'); if (!isset($relatedCases)) { - $relatedCases = CRM_Case_BAO_Case::getRelatedCases($caseId, $form->_currentlyViewedContactId); + $relatedCases = CRM_Case_BAO_Case::getRelatedCases($caseId); $form->set('relatedCases', empty($relatedCases) ? FALSE : $relatedCases); } } @@ -70,7 +71,11 @@ public static function preProcess(&$form) { * @return array */ public static function setDefaultValues(&$form) { - return $defaults = array(); + $defaults = []; + if (!empty($_GET['link_to_case_id']) && CRM_Utils_Rule::positiveInteger($_GET['link_to_case_id'])) { + $defaults['link_to_case_id'] = $_GET['link_to_case_id']; + } + return $defaults; } /** @@ -82,16 +87,16 @@ public static function buildQuickForm(&$form) { if (is_array($relatedCases) && !empty($relatedCases)) { $excludeCaseIds = array_merge($excludeCaseIds, array_keys($relatedCases)); } - $form->addEntityRef('link_to_case_id', ts('Link To Case'), array( + $form->addEntityRef('link_to_case_id', ts('Link To Case'), [ 'entity' => 'Case', - 'api' => array( - 'extra' => array('case_id.case_type_id.title', 'contact_id.sort_name'), - 'params' => array( - 'case_id' => array('NOT IN' => $excludeCaseIds), + 'api' => [ + 'extra' => ['case_id.case_type_id.title', 'contact_id.sort_name'], + 'params' => [ + 'case_id' => ['NOT IN' => $excludeCaseIds], 'case_id.is_deleted' => 0, - ), - ), - ), TRUE); + ], + ], + ], TRUE); } /** @@ -107,7 +112,7 @@ public static function buildQuickForm(&$form) { * list of errors to be posted back to the form */ public static function formRule($values, $files, $form) { - $errors = array(); + $errors = []; $linkCaseId = CRM_Utils_Array::value('link_to_case_id', $values); assert('is_numeric($linkCaseId)'); @@ -148,10 +153,10 @@ public static function endPostProcess(&$form, &$params, &$activity) { //create a link between two cases. if ($activityId && $linkCaseID) { - $caseParams = array( + $caseParams = [ 'case_id' => $linkCaseID, 'activity_id' => $activityId, - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); } } diff --git a/CRM/Case/Form/Activity/OpenCase.php b/CRM/Case/Form/Activity/OpenCase.php index 45bac38192c6..c233fd321a61 100644 --- a/CRM/Case/Form/Activity/OpenCase.php +++ b/CRM/Case/Form/Activity/OpenCase.php @@ -1,9 +1,9 @@ _context == 'caseActivity') { $contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $form); - $atype = CRM_Core_OptionGroup::getValue('activity_type', - 'Change Case Start Date', - 'name' - ); + $atype = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Case Start Date'); $caseId = CRM_Utils_Array::first($form->_caseId); $form->assign('changeStartURL', CRM_Utils_System::url('civicrm/case/activity', "action=add&reset=1&cid=$contactID&caseid={$caseId}&atype=$atype" @@ -65,7 +64,7 @@ public static function preProcess(&$form) { return; } - $form->_context = CRM_Utils_Request::retrieve('context', 'String', $form); + $form->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $form); $form->_contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $form); $form->assign('context', $form->_context); @@ -89,16 +88,17 @@ public static function preProcess(&$form) { * Set default values for the form. For edit/view mode * the default values are retrieved from the database * + * @param CRM_Case_Form_Case $form * - * @param CRM_Core_Form $form + * @return array $defaults */ public static function setDefaultValues(&$form) { - $defaults = array(); + $defaults = []; if ($form->_context == 'caseActivity') { return $defaults; } - list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults(NULL, 'activityDateTime'); + $defaults['start_date'] = date('Y-m-d H:i:s'); // set default case status, case type, encounter medium, location type and phone type defaults are set in DB if ($form->_caseStatusId) { @@ -107,7 +107,8 @@ public static function setDefaultValues(&$form) { else { $caseStatus = CRM_Core_OptionGroup::values('case_status', FALSE, FALSE, FALSE, 'AND is_default = 1'); if (count($caseStatus) == 1) { - $caseStatus = key($caseStatus); //$defaults['status_id'] = key($caseStatus); + //$defaults['status_id'] = key($caseStatus); + $caseStatus = key($caseStatus); } } $defaults['status_id'] = $caseStatus; @@ -152,30 +153,30 @@ public static function buildQuickForm(&$form) { return; } if ($form->_context == 'standalone') { - $form->addEntityRef('client_id', ts('Client'), array( - 'create' => TRUE, - 'multiple' => $form->_allowMultiClient, - ), TRUE); + $form->addEntityRef('client_id', ts('Client'), [ + 'create' => TRUE, + 'multiple' => $form->_allowMultiClient, + ], TRUE); } - $element = $form->addField('case_type_id', array( + $element = $form->addField('case_type_id', [ 'context' => 'create', 'entity' => 'Case', 'onchange' => "CRM.buildCustomData('Case', this.value);", - ), TRUE); + ], TRUE); if ($form->_caseTypeId) { $element->freeze(); } - $csElement = $form->addField('status_id', array( + $csElement = $form->addField('status_id', [ 'context' => 'create', 'entity' => 'Case', - ), TRUE); + ], TRUE); if ($form->_caseStatusId) { $csElement->freeze(); } - $form->add('text', 'duration', ts('Activity Duration'), array('size' => 4, 'maxlength' => 8)); + $form->add('number', 'duration', ts('Activity Duration'), ['class' => 'four', 'min' => 1]); $form->addRule('duration', ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'); if ($form->_currentlyViewedContactId) { @@ -183,39 +184,37 @@ public static function buildQuickForm(&$form) { $form->assign('clientName', $displayName); } - $form->addDate('start_date', ts('Case Start Date'), TRUE, array('formatType' => 'activityDateTime')); + $form->add('datepicker', 'start_date', ts('Case Start Date'), [], TRUE); - $form->addField('medium_id', array('entity' => 'activity', 'context' => 'create'), TRUE); + $form->addField('medium_id', ['entity' => 'activity', 'context' => 'create'], TRUE); // calling this field activity_location to prevent conflict with contact location fields $form->add('text', 'activity_location', ts('Location'), CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'location')); - $form->add('wysiwyg', 'activity_details', ts('Details'), array('rows' => 4, 'cols' => 60), FALSE); - - $form->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'upload', - 'name' => ts('Save and New'), - 'subName' => 'new', - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $form->add('wysiwyg', 'activity_details', ts('Details'), ['rows' => 4, 'cols' => 60], FALSE); + + $form->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'upload', + 'name' => ts('Save and New'), + 'subName' => 'new', + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** * Process the form submission. * - * - * @param CRM_Core_Form $form + * @param CRM_Case_Form_Case $form * @param array $params */ public static function beginPostProcess(&$form, &$params) { @@ -228,14 +227,6 @@ public static function beginPostProcess(&$form, &$params) { $form->_currentlyViewedContactId = $params['client_id'][0]; } - // for open case start date should be set to current date - $params['start_date'] = CRM_Utils_Date::processDate($params['start_date'], $params['start_date_time']); - $caseStatus = CRM_Case_PseudoConstant::caseStatus('name'); - // for resolved case the end date should set to now - if ($params['status_id'] == array_search('Closed', $caseStatus)) { - $params['end_date'] = $params['now']; - } - // rename activity_location param to the correct column name for activity DAO $params['location'] = CRM_Utils_Array::value('activity_location', $params); @@ -254,7 +245,7 @@ public static function beginPostProcess(&$form, &$params) { * * @param $fields * @param $files - * @param CRM_Core_Form $form + * @param CRM_Case_Form_Case $form * * @return array * list of errors to be posted back to the form @@ -264,15 +255,17 @@ public static function formRule($fields, $files, $form) { return TRUE; } - $errors = array(); + $errors = []; return $errors; } /** * Process the form submission. * - * @param CRM_Core_Form $form + * @param CRM_Case_Form_Case $form * @param array $params + * + * @throws \Exception */ public static function endPostProcess(&$form, &$params) { if ($form->_context == 'caseActivity') { @@ -299,25 +292,25 @@ public static function endPostProcess(&$form, &$params) { if (empty($cliId)) { CRM_Core_Error::fatal('client_id cannot be empty'); } - $contactParams = array( + $contactParams = [ 'case_id' => $params['case_id'], 'contact_id' => $cliId, - ); + ]; CRM_Case_BAO_CaseContact::create($contactParams); } } else { - $contactParams = array( + $contactParams = [ 'case_id' => $params['case_id'], 'contact_id' => $form->_currentlyViewedContactId, - ); + ]; CRM_Case_BAO_CaseContact::create($contactParams); } // 2. initiate xml processor $xmlProcessor = new CRM_Case_XMLProcessor_Process(); - $xmlProcessorParams = array( + $xmlProcessorParams = [ 'clientID' => $form->_currentlyViewedContactId, 'creatorID' => $form->_currentUserId, 'standardTimeline' => 1, @@ -329,7 +322,8 @@ public static function endPostProcess(&$form, &$params) { 'duration' => CRM_Utils_Array::value('duration', $params), 'medium_id' => $params['medium_id'], 'details' => $params['activity_details'], - ); + 'relationship_end_date' => CRM_Utils_Array::value('end_date', $params), + ]; if (array_key_exists('custom', $params) && is_array($params['custom'])) { $xmlProcessorParams['custom'] = $params['custom']; diff --git a/CRM/Case/Form/ActivityToCase.php b/CRM/Case/Form/ActivityToCase.php index 7951af703103..547a7ae208ab 100644 --- a/CRM/Case/Form/ActivityToCase.php +++ b/CRM/Case/Form/ActivityToCase.php @@ -1,9 +1,9 @@ _currentCaseId = CRM_Utils_Request::retrieve('caseId', 'Positive'); $this->assign('currentCaseId', $this->_currentCaseId); $this->assign('buildCaseActivityForm', TRUE); + + switch (CRM_Utils_Request::retrieve('fileOnCaseAction', 'String')) { + case 'move': + CRM_Utils_System::setTitle(ts('Move to Case')); + break; + + case 'copy': + CRM_Utils_System::setTitle(ts('Copy to Case')); + break; + + } } /** @@ -58,8 +69,8 @@ public function preProcess() { * @return array */ public function setDefaultValues() { - $defaults = array(); - $params = array('id' => $this->_activityId); + $defaults = []; + $params = ['id' => $this->_activityId]; CRM_Activity_BAO_Activity::retrieve($params, $defaults); $defaults['file_on_case_activity_subject'] = $defaults['subject']; @@ -67,16 +78,22 @@ public function setDefaultValues() { // If this contact has an open case, supply it as a default $cid = CRM_Utils_Request::retrieve('cid', 'Integer'); + if (!$cid) { + $act = civicrm_api3('Activity', 'getsingle', ['id' => $this->_activityId, 'return' => 'target_contact_id']); + if (!empty($act['target_contact_id'])) { + $cid = $act['target_contact_id'][0]; + } + } if ($cid) { - $cases = civicrm_api3('CaseContact', 'get', array( + $cases = civicrm_api3('CaseContact', 'get', [ 'contact_id' => $cid, - 'case_id' => array('!=' => $this->_currentCaseId), - 'case_id.status_id' => array('!=' => "Closed"), + 'case_id' => ['!=' => $this->_currentCaseId], + 'case_id.status_id' => ['!=' => "Closed"], 'case_id.is_deleted' => 0, - 'case_id.end_date' => array('IS NULL' => 1), - 'options' => array('limit' => 1), + 'case_id.end_date' => ['IS NULL' => 1], + 'options' => ['limit' => 1], 'return' => 'case_id', - )); + ]); foreach ($cases['values'] as $record) { $defaults['file_on_case_unclosed_case_id'] = $record['case_id']; break; @@ -89,20 +106,20 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - $this->addEntityRef('file_on_case_unclosed_case_id', ts('Select Case'), array( + $this->addEntityRef('file_on_case_unclosed_case_id', ts('Select Case'), [ 'entity' => 'Case', - 'api' => array( - 'extra' => array('contact_id'), - 'params' => array( - 'case_id' => array('!=' => $this->_currentCaseId), + 'api' => [ + 'extra' => ['contact_id'], + 'params' => [ + 'case_id' => ['!=' => $this->_currentCaseId], 'case_id.is_deleted' => 0, - 'case_id.status_id' => array('!=' => "Closed"), - 'case_id.end_date' => array('IS NULL' => 1), - ), - ), - ), TRUE); - $this->addEntityRef('file_on_case_target_contact_id', ts('With Contact(s)'), array('multiple' => TRUE)); - $this->add('text', 'file_on_case_activity_subject', ts('Subject'), array('size' => 50)); + 'case_id.status_id' => ['!=' => "Closed"], + 'case_id.end_date' => ['IS NULL' => 1], + ], + ], + ], TRUE); + $this->addEntityRef('file_on_case_target_contact_id', ts('With Contact(s)'), ['multiple' => TRUE]); + $this->add('text', 'file_on_case_activity_subject', ts('Subject'), ['size' => 50]); } } diff --git a/CRM/Case/Form/ActivityView.php b/CRM/Case/Form/ActivityView.php index a772ef6ead47..b18be74fdc36 100644 --- a/CRM/Case/Form/ActivityView.php +++ b/CRM/Case/Form/ActivityView.php @@ -1,9 +1,9 @@ 'Attachment(s)', 'value' => $attachmentUrl, 'type' => 'Link', - ); + ]; } $tags = CRM_Core_BAO_EntityTag::getTag($activityID, 'civicrm_activity'); if (!empty($tags)) { - $allTag = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $allTag = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); foreach ($tags as $tid) { $tags[$tid] = $allTag[$tid]; } - $report['fields'][] = array( + $report['fields'][] = [ 'label' => 'Tags', 'value' => implode('
    ', $tags), 'type' => 'String', - ); + ]; } $this->assign('report', $report); $latestRevisionID = CRM_Activity_BAO_Activity::getLatestActivityId($activityID); - $viewPriorActivities = array(); + $viewPriorActivities = []; $priorActivities = CRM_Activity_BAO_Activity::getPriorAcitivities($activityID); foreach ($priorActivities as $activityId => $activityValues) { if (CRM_Case_BAO_Case::checkPermission($activityId, 'view', NULL, $contactID)) { @@ -123,7 +123,7 @@ public function preProcess() { //viewing activity should get diplayed in recent list.CRM-4670 $activityTypeID = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity', $activityID, 'activity_type_id'); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $activityTargetContacts = CRM_Activity_BAO_ActivityContact::retrieveContactIdsByActivityId($activityID, $targetID); if (!empty($activityTargetContacts)) { @@ -152,7 +152,7 @@ public function preProcess() { $title = $title . $recentContactDisplay . ' (' . $activityTypes[$activityTypeID] . ')'; - $recentOther = array(); + $recentOther = []; if (CRM_Case_BAO_Case::checkPermission($activityID, 'edit')) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/case/activity', "reset=1&action=update&id={$activityID}&cid={$recentContactId}&caseid={$caseID}&context=home" @@ -172,6 +172,38 @@ public function preProcess() { $recentContactDisplay, $recentOther ); + + // Set breadcrumb to take the user back to the case being viewed + $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'case_type_id'); + $caseType = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'case_type_id', $caseTypeId); + $caseContact = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseContact', $caseID, 'contact_id', 'case_id'); + + CRM_Utils_System::resetBreadCrumb(); + $breadcrumb = [ + [ + 'title' => ts('Home'), + 'url' => CRM_Utils_System::url(), + ], + [ + 'title' => ts('CiviCRM'), + 'url' => CRM_Utils_System::url('civicrm', 'reset=1'), + ], + [ + 'title' => ts('CiviCase Dashboard'), + 'url' => CRM_Utils_System::url('civicrm/case', 'reset=1'), + ], + [ + 'title' => $caseType, + 'url' => CRM_Utils_System::url('civicrm/contact/view/case', [ + 'reset' => 1, + 'id' => $caseID, + 'context' => 'case', + 'action' => 'view', + 'cid' => $caseContact, + ]), + ], + ]; + CRM_Utils_System::appendBreadCrumb($breadcrumb); } } diff --git a/CRM/Case/Form/AddToCaseAsRole.php b/CRM/Case/Form/AddToCaseAsRole.php new file mode 100644 index 000000000000..f2809047e3c9 --- /dev/null +++ b/CRM/Case/Form/AddToCaseAsRole.php @@ -0,0 +1,101 @@ +getRoleTypes(); + $this->add( + 'select', + 'role_type', + ts('Relationship Type'), + ['' => ts('- select type -')] + $roleTypes, + TRUE, + ['class' => 'crm-select2 twenty'] + ); + + $this->addEntityRef( + 'assign_to', + ts('Assign to'), + ['entity' => 'Case'], + TRUE + ); + + $this->addButtons([ + [ + 'type' => 'submit', + 'name' => ts('Submit'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + } + + /** + * Returns list of configured role types for individuals. + * + * @return array + * List of role types + */ + private function getRoleTypes() { + $relType = CRM_Contact_BAO_Relationship::getRelationType('Individual'); + $roleTypes = []; + + foreach ($relType as $k => $v) { + $roleTypes[substr($k, 0, strpos($k, '_'))] = $v; + } + + return $roleTypes; + } + + /** + * @inheritdoc + */ + public function postProcess() { + $values = $this->controller->exportValues(); + + $caseId = (int) $values['assign_to']; + $roleTypeId = (int) $values['role_type']; + $contacts = $this->_contactIds; + + $clients = CRM_Case_BAO_Case::getCaseClients($caseId); + $caseRole = CRM_Case_BAO_Case::getCaseRoleDirection($caseId, $roleTypeId); + + $params = [ + 'case_id' => $caseId, + 'relationship_type_id' => $roleTypeId, + ]; + + if ($caseRole[$roleTypeId]['direction'] == 'b_a') { + $params['contact_id_b'] = $clients[0]; + $params['contact_id_a'] = $contacts; + CRM_Contact_BAO_Relationship::createMultiple($params, 'b'); + } + elseif ($caseRole[$roleTypeId]['direction'] == 'a_b' || $caseRole[$roleTypeId]['direction'] = 'bidirectional') { + $params['contact_id_a'] = $clients[0]; + $params['contact_id_b'] = $contacts; + CRM_Contact_BAO_Relationship::createMultiple($params, 'a'); + } + + $url = CRM_Utils_System::url( + 'civicrm/contact/view/case', + [ + 'cid' => $clients[0], + 'id' => $caseId, + 'reset' => 1, + 'action' => 'view', + ] + ); + CRM_Utils_System::redirect($url); + } + +} diff --git a/CRM/Case/Form/Case.php b/CRM/Case/Form/Case.php index 91658cee6017..80b83d578cde 100644 --- a/CRM/Case/Form/Case.php +++ b/CRM/Case/Form/Case.php @@ -1,9 +1,9 @@ _caseId; + } + + /** + * Get the entity subtype ID being edited + * + * @param $subTypeId + * + * @return int|null + */ + public function getEntitySubTypeId($subTypeId) { + if ($subTypeId) { + return $subTypeId; + } + return $this->_caseTypeId; + } + /** * Build the form object. */ public function preProcess() { + if (empty($this->_action)) { + $this->_action = CRM_Core_Action::ADD; + } $this->_caseId = CRM_Utils_Request::retrieve('id', 'Positive', $this); @@ -110,24 +153,21 @@ public function preProcess() { } if (!$this->_caseId) { - $caseAttributes = array( + $caseAttributes = [ 'case_type_id' => ts('Case Type'), 'status_id' => ts('Case Status'), 'medium_id' => ts('Activity Medium'), - ); + ]; foreach ($caseAttributes as $key => $label) { if (!CRM_Case_BAO_Case::buildOptions($key, 'create')) { - CRM_Core_Error::fatal(ts('You do not have any active %1', array(1 => $label))); + CRM_Core_Error::fatal(ts('You do not have any active %1', [1 => $label])); } } } if ($this->_action & CRM_Core_Action::ADD) { - $this->_activityTypeId = CRM_Core_OptionGroup::getValue('activity_type', - 'Open Case', - 'name' - ); + $this->_activityTypeId = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case'); if (!$this->_activityTypeId) { CRM_Core_Error::fatal(ts('The Open Case activity type is missing or disabled. Please have your site administrator check Administer > Option Lists > Activity Types for the CiviCase component.')); } @@ -162,7 +202,7 @@ public function preProcess() { $contact = new CRM_Contact_DAO_Contact(); $contact->id = $this->_currentlyViewedContactId; if (!$contact->find(TRUE)) { - CRM_Core_Error::statusBounce(ts('Client contact does not exist: %1', array(1 => $this->_currentlyViewedContactId))); + CRM_Core_Error::statusBounce(ts('Client contact does not exist: %1', [1 => $this->_currentlyViewedContactId])); } $this->assign('clientName', $contact->display_name); } @@ -170,18 +210,15 @@ public function preProcess() { $session = CRM_Core_Session::singleton(); $this->_currentUserId = $session->get('userID'); - //when custom data is included in this page + //Add activity custom data is included in this page CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_activityTypeId, 1, 'Activity'); $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; $className::preProcess($this); $activityGroupTree = $this->_groupTree; - // for case custom fields to populate with defaults - if (!empty($_POST['hidden_custom'])) { - $params = CRM_Utils_Request::exportValues(); - CRM_Custom_Form_CustomData::preProcess($this, NULL, CRM_Utils_Array::value('case_type_id', $params, $this->_caseTypeId), 1, 'Case', $this->_caseId); - CRM_Custom_Form_CustomData::buildQuickForm($this); - } + // Add case custom data to form + $caseTypeId = CRM_Utils_Array::value('case_type_id', CRM_Utils_Request::exportValues(), $this->_caseTypeId); + CRM_Custom_Form_CustomData::addToForm($this, $caseTypeId); // so that grouptree is not populated with case fields, since the grouptree is used // for populating activity custom fields. @@ -207,48 +244,46 @@ public function buildQuickForm() { $this->assign('multiClient', $isMultiClient); if ($this->_action & CRM_Core_Action::DELETE || $this->_action & CRM_Core_Action::RENEW) { - $title = 'Delete'; + $title = ts('Delete'); if ($this->_action & CRM_Core_Action::RENEW) { - $title = 'Restore'; + $title = ts('Restore'); } - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => $title, - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => $title, + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); return; } - //need to assign custom data type and subtype to the template - $this->assign('customDataType', 'Case'); - + // Add the activity custom data to the form CRM_Custom_Form_CustomData::buildQuickForm($this); + // we don't want to show button on top of custom form $this->assign('noPreCustomButton', TRUE); $s = CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'subject'); if (!is_array($s)) { - $s = array(); + $s = []; } $this->add('text', 'activity_subject', ts('Subject'), - array_merge($s, array( + array_merge($s, [ 'maxlength' => '128', - )), TRUE + ]), TRUE ); $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_case'); if (!empty($tags)) { $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, - array('class' => 'huge', 'multiple' => 'multiple') + ['class' => 'huge', 'multiple' => 'multiple'] ); } @@ -256,19 +291,18 @@ public function buildQuickForm() { $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_case'); CRM_Core_Form_Tag::buildQuickForm($this, $parentNames, 'civicrm_case', NULL, FALSE, TRUE); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Save'), - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; $className::buildQuickForm($this); @@ -284,8 +318,8 @@ public function addRules() { return TRUE; } $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; - $this->addFormRule(array($className, 'formRule'), $this); - $this->addFormRule(array('CRM_Case_Form_Case', 'formRule'), $this); + $this->addFormRule([$className, 'formRule'], $this); + $this->addFormRule(['CRM_Case_Form_Case', 'formRule'], $this); } /** @@ -305,34 +339,30 @@ public static function formRule($values, $files, $form) { } /** - * Process the form submission. + * Wrapper for unit testing the post process submit function. + * + * @param $params + * @param $activityTypeFile + * @param $contactId + * @param $context + * @return CRM_Case_BAO_Case */ - public function postProcess() { - $transaction = new CRM_Core_Transaction(); + public function testSubmit($params, $activityTypeFile, $contactId, $context = "case") { + $this->controller = new CRM_Core_Controller(); - // check if dedupe button, if so return. - $buttonName = $this->controller->getButtonName(); - if (isset($this->_dedupeButtonName) && $buttonName == $this->_dedupeButtonName) { - return; - } + $this->_activityTypeFile = $activityTypeFile; + $this->_currentUserId = $contactId; + $this->_context = $context; - if ($this->_action & CRM_Core_Action::DELETE) { - $caseDelete = CRM_Case_BAO_Case::deleteCase($this->_caseId, TRUE); - if ($caseDelete) { - CRM_Core_Session::setStatus(ts('You can view and / or restore deleted cases by checking the "Deleted Cases" option under Find Cases.'), ts('Case Deleted'), 'success'); - } - return; - } + return $this->submit($params); + } - if ($this->_action & CRM_Core_Action::RENEW) { - $caseRestore = CRM_Case_BAO_Case::restoreCase($this->_caseId); - if ($caseRestore) { - CRM_Core_Session::setStatus(ts('The selected case has been restored.'), ts('Restored'), 'success'); - } - return; - } - // store the submitted values in an array - $params = $this->controller->exportValues($this->_name); + /** + * Submit the form with given params. + * + * @param $params + */ + public function submit(&$params) { $params['now'] = date("Ymd"); // 1. call begin post process @@ -357,14 +387,14 @@ public function postProcess() { $params['subject'] = $params['activity_subject']; } $caseObj = CRM_Case_BAO_Case::create($params); - $params['case_id'] = $caseObj->id; + $this->_caseId = $params['case_id'] = $caseObj->id; // unset any ids, custom data unset($params['id'], $params['custom']); // add tags if exists - $tagParams = array(); + $tagParams = []; if (!empty($params['tag'])) { - $tagParams = array(); + $tagParams = []; if (!is_array($params['tag'])) { $params['tag'] = explode(',', $params['tag']); } @@ -404,7 +434,42 @@ public function postProcess() { $className::endPostProcess($this, $params); } + return $caseObj; + } + + /** + * Process the form submission. + */ + public function postProcess() { + $transaction = new CRM_Core_Transaction(); + + // check if dedupe button, if so return. + $buttonName = $this->controller->getButtonName(); + if (isset($this->_dedupeButtonName) && $buttonName == $this->_dedupeButtonName) { + return; + } + + if ($this->_action & CRM_Core_Action::DELETE) { + $caseDelete = CRM_Case_BAO_Case::deleteCase($this->_caseId, TRUE); + if ($caseDelete) { + CRM_Core_Session::setStatus(ts('You can view and / or restore deleted cases by checking the "Deleted Cases" option under Find Cases.'), ts('Case Deleted'), 'success'); + } + return; + } + + if ($this->_action & CRM_Core_Action::RENEW) { + $caseRestore = CRM_Case_BAO_Case::restoreCase($this->_caseId); + if ($caseRestore) { + CRM_Core_Session::setStatus(ts('The selected case has been restored.'), ts('Restored'), 'success'); + } + return; + } + // store the submitted values in an array + $params = $this->controller->exportValues($this->_name); + $this->submit($params); + CRM_Core_Session::setStatus($params['statusMsg'], ts('Saved'), 'success'); + } } diff --git a/CRM/Case/Form/CaseView.php b/CRM/Case/Form/CaseView.php index fac08c11a27f..2f9eb82a0e8c 100644 --- a/CRM/Case/Form/CaseView.php +++ b/CRM/Case/Form/CaseView.php @@ -1,9 +1,9 @@ assign('relatedCases', $relatedCases); $this->assign('showRelatedCases', TRUE); @@ -74,10 +74,10 @@ public function preProcess() { // Access check. if (!CRM_Case_BAO_Case::accessCase($this->_caseID, FALSE)) { - CRM_Core_Error::fatal(ts('You are not authorized to access this page.')); + CRM_Core_Error::statusBounce(ts('You do not have permission to access this case.')); } - $fulltext = CRM_Utils_Request::retrieve('context', 'String'); + $fulltext = CRM_Utils_Request::retrieve('context', 'Alphanumeric'); if ($fulltext == 'fulltext') { $this->assign('fulltext', $fulltext); } @@ -86,21 +86,21 @@ public function preProcess() { $this->assign('userID', CRM_Core_Session::getLoggedInContactID()); //retrieve details about case - $params = array('id' => $this->_caseID); + $params = ['id' => $this->_caseID]; - $returnProperties = array('case_type_id', 'subject', 'status_id', 'start_date'); + $returnProperties = ['case_type_id', 'subject', 'status_id', 'start_date']; CRM_Core_DAO::commonRetrieve('CRM_Case_BAO_Case', $params, $values, $returnProperties); $statuses = CRM_Case_PseudoConstant::caseStatus('label', FALSE); $caseTypeName = CRM_Case_BAO_Case::getCaseType($this->_caseID, 'name'); $caseType = CRM_Case_BAO_Case::getCaseType($this->_caseID); - $this->_caseDetails = array( + $this->_caseDetails = [ 'case_type' => $caseType, 'case_status' => CRM_Utils_Array::value($values['case_status_id'], $statuses), 'case_subject' => CRM_Utils_Array::value('subject', $values), 'case_start_date' => $values['case_start_date'], - ); + ]; $this->_caseType = $caseTypeName; $this->assign('caseDetails', $this->_caseDetails); @@ -121,7 +121,7 @@ public function preProcess() { CRM_Utils_System::setTitle($displayName . ' - ' . $caseType); - $recentOther = array(); + $recentOther = []; if (CRM_Core_Permission::checkActionPermission('CiviCase', CRM_Core_Action::DELETE)) { $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/case', "action=delete&reset=1&id={$this->_caseID}&cid={$this->_contactID}&context=home" @@ -141,28 +141,28 @@ public function preProcess() { //get the related cases for given case. $relatedCases = $this->get('relatedCases'); if (!isset($relatedCases)) { - $relatedCases = CRM_Case_BAO_Case::getRelatedCases($this->_caseID, $this->_contactID); + $relatedCases = CRM_Case_BAO_Case::getRelatedCases($this->_caseID); $relatedCases = empty($relatedCases) ? FALSE : $relatedCases; $this->set('relatedCases', $relatedCases); } $this->assign('hasRelatedCases', (bool) $relatedCases); if ($relatedCases) { - $this->assign('relatedCaseLabel', ts('%1 Related Case', array( - 'count' => count($relatedCases), - 'plural' => '%1 Related Cases', - ))); - $this->assign('relatedCaseUrl', CRM_Utils_System::url('civicrm/contact/view/case', array( + $this->assign('relatedCaseLabel', ts('%1 Related Case', [ + 'count' => count($relatedCases), + 'plural' => '%1 Related Cases', + ])); + $this->assign('relatedCaseUrl', CRM_Utils_System::url('civicrm/contact/view/case', [ 'id' => $this->_caseID, 'cid' => $this->_contactID, 'relatedCases' => 1, 'action' => 'view', - ))); + ])); } $entitySubType = !empty($values['case_type_id']) ? $values['case_type_id'] : NULL; $this->assign('caseTypeID', $entitySubType); $groupTree = CRM_Core_BAO_CustomGroup::getTree('Case', - $this, + NULL, $this->_caseID, NULL, $entitySubType @@ -176,7 +176,7 @@ public function preProcess() { * @return array; */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; return $defaults; } @@ -208,8 +208,7 @@ public function buildQuickForm() { $aTypes = $xmlProcessor->get($this->_caseType, 'ActivityTypes', TRUE); - $allActTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name'); - + $allActTypes = CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate'); $emailActivityType = array_search('Email', $allActTypes); $pdfActivityType = array_search('Print PDF Letter', $allActTypes); @@ -226,11 +225,11 @@ public function buildQuickForm() { // Only show "link cases" activity if other cases exist. $linkActTypeId = array_search('Link Cases', $allActTypes); if ($linkActTypeId) { - $count = civicrm_api3('Case', 'getcount', array( + $count = civicrm_api3('Case', 'getcount', [ 'check_permissions' => TRUE, - 'id' => array('!=' => $this->_caseID), + 'id' => ['!=' => $this->_caseID], 'is_deleted' => 0, - )); + ]); if (!$count) { unset($aTypes[$linkActTypeId]); } @@ -240,7 +239,7 @@ public function buildQuickForm() { asort($aTypes); } - $activityLinks = array('' => ts('Add Activity')); + $activityLinks = ['' => ts('Add Activity')]; foreach ($aTypes as $type => $label) { if ($type == $emailActivityType) { $url = CRM_Utils_System::url('civicrm/activity/email/add', @@ -262,20 +261,20 @@ public function buildQuickForm() { $activityLinks[$url] = $label; } - $this->add('select', 'add_activity_type_id', '', $activityLinks, FALSE, array('class' => 'crm-select2 crm-action-menu fa-calendar-check-o twenty')); + $this->add('select', 'add_activity_type_id', '', $activityLinks, FALSE, ['class' => 'crm-select2 crm-action-menu fa-calendar-check-o twenty']); if ($this->_hasAccessToAllCases) { $this->add('select', 'report_id', '', - array('' => ts('Activity Audit')) + $reports, + ['' => ts('Activity Audit')] + $reports, FALSE, - array('class' => 'crm-select2 crm-action-menu fa-list-alt') + ['class' => 'crm-select2 crm-action-menu fa-list-alt'] ); $this->add('select', 'timeline_id', '', - array('' => ts('Add Timeline')) + $reports, + ['' => ts('Add Timeline')] + $reports, FALSE, - array('class' => 'crm-select2 crm-action-menu fa-list-ol') + ['class' => 'crm-select2 crm-action-menu fa-list-ol'] ); } - $this->addElement('submit', $this->getButtonName('next'), ' ', array('class' => 'hiddenElement')); + $this->addElement('submit', $this->getButtonName('next'), ' ', ['class' => 'hiddenElement']); $this->buildMergeCaseForm(); @@ -290,7 +289,7 @@ public function buildQuickForm() { foreach ($caseRelationships as $key => & $value) { if (!empty($managerRoleId)) { - if ($managerRoleId == $value['relation_type']) { + if (substr($managerRoleId, 0, -4) == $value['relation_type'] && substr($managerRoleId, -3) == $value['relationship_direction']) { $value['relation'] = $managerLabel; } } @@ -316,7 +315,7 @@ public function buildQuickForm() { // Now build 'Other Relationships' array by removing relationships that are already listed under Case Roles // so they don't show up twice. - $clientRelationships = array(); + $clientRelationships = []; foreach ($relClient as $r) { if (!array_key_exists($r['id'], $caseRelationships)) { $clientRelationships[] = $r; @@ -325,7 +324,7 @@ public function buildQuickForm() { $this->assign('clientRelationships', $clientRelationships); // Now global contact list that appears on all cases. - $globalGroupInfo = array(); + $globalGroupInfo = []; CRM_Case_BAO_Case::getGlobalContacts($globalGroupInfo); $this->assign('globalGroupInfo', $globalGroupInfo); @@ -333,9 +332,9 @@ public function buildQuickForm() { $this->add('select', 'role_type', ts('Relationship Type'), - array('' => ts('- select type -')) + $allowedRelationshipTypes, + ['' => ts('- select type -')] + $allowedRelationshipTypes, FALSE, - array('class' => 'crm-select2 twenty', 'data-select-params' => '{"allowClear": false}') + ['class' => 'crm-select2 twenty', 'data-select-params' => '{"allowClear": false}'] ); $hookCaseSummary = CRM_Utils_Hook::caseSummary($this->_caseID); @@ -347,7 +346,7 @@ public function buildQuickForm() { if (!empty($allTags)) { $this->add('select2', 'case_tag', ts('Tags'), $allTags, FALSE, - array('id' => 'tags', 'multiple' => 'multiple') + ['id' => 'tags', 'multiple' => 'multiple'] ); $tags = CRM_Core_BAO_EntityTag::getTag($this->_caseID, 'civicrm_case'); @@ -362,7 +361,7 @@ public function buildQuickForm() { } } - $this->setDefaults(array('case_tag' => implode(',', array_keys($tags)))); + $this->setDefaults(['case_tag' => implode(',', array_keys($tags))]); $this->assign('tags', $tags); $this->assign('showTags', TRUE); @@ -375,38 +374,37 @@ public function buildQuickForm() { // see if we have any tagsets which can be assigned to cases $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_case'); - $tagSetTags = array(); + $tagSetTags = []; if ($parentNames) { $this->assign('showTags', TRUE); - $tagSetItems = civicrm_api3('entityTag', 'get', array( + $tagSetItems = civicrm_api3('entityTag', 'get', [ 'entity_id' => $this->_caseID, 'entity_table' => 'civicrm_case', 'tag_id.parent_id.is_tagset' => 1, - 'options' => array('limit' => 0), - 'return' => array("tag_id.parent_id", "tag_id.parent_id.name", "tag_id.name"), - )); + 'options' => ['limit' => 0], + 'return' => ["tag_id.parent_id", "tag_id.parent_id.name", "tag_id.name"], + ]); foreach ($tagSetItems['values'] as $tag) { - $tagSetTags += array( - $tag['tag_id.parent_id'] => array( + $tagSetTags += [ + $tag['tag_id.parent_id'] => [ 'name' => $tag['tag_id.parent_id.name'], - 'items' => array(), - ), - ); + 'items' => [], + ], + ]; $tagSetTags[$tag['tag_id.parent_id']]['items'][] = $tag['tag_id.name']; } } $this->assign('tagSetTags', $tagSetTags); CRM_Core_Form_Tag::buildQuickForm($this, $parentNames, 'civicrm_case', $this->_caseID, FALSE, TRUE); - $this->addButtons(array( - array( - 'type' => 'cancel', - 'name' => ts('Done'), - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'cancel', + 'name' => ts('Done'), + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + ]); } /** @@ -424,15 +422,15 @@ public function postProcess() { $session->pushUserContext($url); if (!empty($params['timeline_id']) && !empty($_POST['_qf_CaseView_next'])) { - civicrm_api3('Case', 'addtimeline', array( + civicrm_api3('Case', 'addtimeline', [ 'case_id' => $this->_caseID, 'timeline' => $params['timeline_id'], - )); + ]); $xmlProcessor = new CRM_Case_XMLProcessor_Process(); $reports = $xmlProcessor->get($this->_caseType, 'ActivitySets'); CRM_Core_Session::setStatus(ts('Activities from the %1 activity set have been added to this case.', - array(1 => $reports[$params['timeline_id']]) + [1 => $reports[$params['timeline_id']]] ), ts('Done'), 'success'); } elseif ($this->_mergeCases && @@ -460,36 +458,36 @@ public function postProcess() { * @param array $aTypes * To include acivities related to current case id $form->_caseID. */ - public static function activityForm($form, $aTypes = array()) { + public static function activityForm($form, $aTypes = []) { $caseRelationships = CRM_Case_BAO_Case::getCaseRoles($form->_contactID, $form->_caseID); //build reporter select - $reporters = array("" => ts(' - any reporter - ')); + $reporters = ["" => ts(' - any reporter - ')]; foreach ($caseRelationships as $key => & $value) { $reporters[$value['cid']] = $value['name'] . " ( {$value['relation']} )"; } - $form->add('select', 'reporter_id', ts('Reporter/Role'), $reporters, FALSE, array('id' => 'reporter_id_' . $form->_caseID)); + $form->add('select', 'reporter_id', ts('Reporter/Role'), $reporters, FALSE, ['id' => 'reporter_id_' . $form->_caseID]); // take all case activity types for search filter, CRM-7187 - $aTypesFilter = array(); + $aTypesFilter = []; $allCaseActTypes = CRM_Case_PseudoConstant::caseActivityType(); foreach ($allCaseActTypes as $typeDetails) { - if (!in_array($typeDetails['name'], array('Open Case'))) { + if (!in_array($typeDetails['name'], ['Open Case'])) { $aTypesFilter[$typeDetails['id']] = CRM_Utils_Array::value('label', $typeDetails); } } $aTypesFilter = $aTypesFilter + $aTypes; asort($aTypesFilter); - $form->add('select', 'activity_type_filter_id', ts('Activity Type'), array('' => ts('- select activity type -')) + $aTypesFilter, FALSE, array('id' => 'activity_type_filter_id_' . $form->_caseID)); + $form->add('select', 'activity_type_filter_id', ts('Activity Type'), ['' => ts('- select activity type -')] + $aTypesFilter, FALSE, ['id' => 'activity_type_filter_id_' . $form->_caseID]); $activityStatus = CRM_Core_PseudoConstant::activityStatus(); - $form->add('select', 'status_id', ts('Status'), array("" => ts(' - any status - ')) + $activityStatus, FALSE, array('id' => 'status_id_' . $form->_caseID)); + $form->add('select', 'status_id', ts('Status'), ["" => ts(' - any status - ')] + $activityStatus, FALSE, ['id' => 'status_id_' . $form->_caseID]); - // activity dates - $form->addDate('activity_date_low_' . $form->_caseID, ts('Activity Dates - From'), FALSE, array('formatType' => 'searchDate')); - $form->addDate('activity_date_high_' . $form->_caseID, ts('To'), FALSE, array('formatType' => 'searchDate')); + // activity date search filters + $form->add('datepicker', 'activity_date_low_' . $form->_caseID, ts('Activity Dates - From'), [], FALSE, ['time' => FALSE]); + $form->add('datepicker', 'activity_date_high_' . $form->_caseID, ts('To'), [], FALSE, ['time' => FALSE]); if (CRM_Core_Permission::check('administer CiviCRM')) { - $form->add('checkbox', 'activity_deleted', ts('Deleted Activities'), '', FALSE, array('id' => 'activity_deleted_' . $form->_caseID)); + $form->add('checkbox', 'activity_deleted', ts('Deleted Activities'), '', FALSE, ['id' => 'activity_deleted_' . $form->_caseID]); } } @@ -497,16 +495,16 @@ public static function activityForm($form, $aTypes = array()) { * Form elements for merging cases */ public function buildMergeCaseForm() { - $otherCases = array(); - $result = civicrm_api3('Case', 'get', array( + $otherCases = []; + $result = civicrm_api3('Case', 'get', [ 'check_permissions' => TRUE, 'contact_id' => $this->_contactID, 'is_deleted' => 0, - 'id' => array('!=' => $this->_caseID), - 'return' => array('id', 'start_date', 'case_type_id.title'), - )); + 'id' => ['!=' => $this->_caseID], + 'return' => ['id', 'start_date', 'case_type_id.title'], + ]); foreach ($result['values'] as $id => $case) { - $otherCases[$id] = "#$id: {$case['case_type_id.title']} " . ts('(opened %1)', array(1 => $case['start_date'])); + $otherCases[$id] = "#$id: {$case['case_type_id.title']} " . ts('(opened %1)', [1 => $case['start_date']]); } $this->assign('mergeCases', $this->_mergeCases = (bool) $otherCases); @@ -514,18 +512,18 @@ public function buildMergeCaseForm() { if ($otherCases) { $this->add('select', 'merge_case_id', ts('Select Case for Merge'), - array( + [ '' => ts('- select case -'), - ) + $otherCases, + ] + $otherCases, FALSE, - array('class' => 'crm-select2 huge') + ['class' => 'crm-select2 huge'] ); $this->addElement('submit', $this->getButtonName('next', 'merge_case'), ts('Merge'), - array( + [ 'class' => 'hiddenElement', - ) + ] ); } } diff --git a/CRM/Case/Form/CustomData.php b/CRM/Case/Form/CustomData.php index 5b2b3c551f09..04723f6609ea 100644 --- a/CRM/Case/Form/CustomData.php +++ b/CRM/Case/Form/CustomData.php @@ -1,9 +1,9 @@ _contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); $groupTree = CRM_Core_BAO_CustomGroup::getTree('Case', - $this, + NULL, $this->_entityID, $this->_groupID, $this->_subTypeID @@ -76,10 +76,10 @@ public function preProcess() { // Array contains only one item foreach ($groupTree as $groupValues) { $this->_customTitle = $groupValues['title']; - CRM_Utils_System::setTitle(ts('Edit %1', array(1 => $groupValues['title']))); + CRM_Utils_System::setTitle(ts('Edit %1', [1 => $groupValues['title']])); } - $this->_defaults = array(); + $this->_defaults = []; CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $this->_defaults); $this->setDefaults($this->_defaults); @@ -98,18 +98,17 @@ public function preProcess() { public function buildQuickForm() { // make this form an upload since we dont know if the custom data injected dynamically // is of type file etc - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -129,30 +128,69 @@ public function postProcess() { $session = CRM_Core_Session::singleton(); $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/view/case', "reset=1&id={$this->_entityID}&cid={$this->_contactID}&action=view")); - $session = CRM_Core_Session::singleton(); - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', 'Change Custom Data', 'name'); - $activityParams = array( + $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Custom Data'); + $activityParams = [ 'activity_type_id' => $activityTypeID, 'source_contact_id' => $session->get('userID'), 'is_auto' => TRUE, 'subject' => $this->_customTitle . " : change data", - 'status_id' => CRM_Core_OptionGroup::getValue('activity_status', - 'Completed', - 'name' - ), + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), 'target_contact_id' => $this->_contactID, - 'details' => json_encode($this->_defaults), + 'details' => $this->formatCustomDataChangesForDetail($params), 'activity_date_time' => date('YmdHis'), - ); + ]; $activity = CRM_Activity_BAO_Activity::create($activityParams); - $caseParams = array( + $caseParams = [ 'activity_id' => $activity->id, 'case_id' => $this->_entityID, - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); $transaction->commit(); } + /** + * Format the custom data changes as [label]: [old value] => [new value] + * + * @param array $params New custom field values from form + * + * @return string + * @throws \CiviCRM_API3_Exception + */ + public function formatCustomDataChangesForDetail($params) { + $formattedDetails = []; + foreach ($params as $customField => $newCustomValue) { + if (substr($customField, 0, 7) == 'custom_') { + if ($this->_defaults[$customField] == $newCustomValue) { + // Don't show values that did not change + continue; + } + // We need custom field ID from custom_XX_1 + list($_, $customFieldId, $_) = explode('_', $customField); + + if (!empty($customFieldId) && is_numeric($customFieldId)) { + // Got a custom field ID + $label = civicrm_api3('CustomField', 'getvalue', ['id' => $customFieldId, 'return' => 'label']); + $oldValue = civicrm_api3('CustomValue', 'getdisplayvalue', [ + 'custom_field_id' => $customFieldId, + 'entity_id' => $this->_entityID, + 'custom_field_value' => $this->_defaults[$customField], + ]); + $oldValue = $oldValue['values'][$customFieldId]['display']; + $newValue = civicrm_api3('CustomValue', 'getdisplayvalue', [ + 'custom_field_id' => $customFieldId, + 'entity_id' => $this->_entityID, + 'custom_field_value' => $newCustomValue, + ]); + $newValue = $newValue['values'][$customFieldId]['display']; + $formattedDetails[] = $label . ': ' . $oldValue . ' => ' . $newValue; + } + + } + } + + return implode('
    ', $formattedDetails); + } + } diff --git a/CRM/Case/Form/EditClient.php b/CRM/Case/Form/EditClient.php index a93cb14e3e64..6c38dd0928dc 100644 --- a/CRM/Case/Form/EditClient.php +++ b/CRM/Case/Form/EditClient.php @@ -1,9 +1,9 @@ assign('currentClientName', CRM_Contact_BAO_Contact::displayName($cid)); @@ -61,10 +61,10 @@ public function preProcess() { elseif ($context == 'dashboard') { $url = CRM_Utils_System::url('civicrm/case', 'reset=1'); } - elseif (in_array($context, array( + elseif (in_array($context, [ 'dashlet', 'dashletFullscreen', - ))) { + ])) { $url = CRM_Utils_System::url('civicrm/dashboard', 'reset=1'); } $session = CRM_Core_Session::singleton(); @@ -75,25 +75,24 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addEntityRef('reassign_contact_id', ts('Select Contact'), array('create' => TRUE), TRUE); - $this->addButtons(array( - array( + $this->addEntityRef('reassign_contact_id', ts('Select Contact'), ['create' => TRUE], TRUE); + $this->addButtons([ + [ 'type' => 'done', 'name' => ts('Reassign Case'), - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); // This form may change the url structure so should not submit via ajax $this->preventAjaxSubmit(); } - public function addRules() { - $this->addFormRule(array(get_class($this), 'formRule'), $this); + $this->addFormRule([get_class($this), 'formRule'], $this); } /** @@ -104,7 +103,7 @@ public function addRules() { * @return array */ public static function formRule($vals, $rule, $form) { - $errors = array(); + $errors = []; if (empty($vals['reassign_contact_id']) || $vals['reassign_contact_id'] == $form->get('cid')) { $errors['reassign_contact_id'] = ts("Please select a different contact."); } diff --git a/CRM/Case/Form/Report.php b/CRM/Case/Form/Report.php index a256d4410ee2..0c926c66bf10 100644 --- a/CRM/Case/Form/Report.php +++ b/CRM/Case/Form/Report.php @@ -1,9 +1,9 @@ ts('All Activities'), 2 => ts('Exclude Completed Activities'), - ); + ]; $includeActivitesGroup = $this->addRadio('include_activities', NULL, $includeActivites, @@ -97,18 +100,17 @@ public function buildQuickForm() { ts('Redact (hide) Client and Service Provider Data') ); - $this->addButtons(array( - array( - 'type' => 'refresh', - 'name' => ts('Generate Report'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'refresh', + 'name' => ts('Generate Report'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); // We want this form to redirect to a full page $this->preventAjaxSubmit(); } diff --git a/CRM/Case/Form/Search.php b/CRM/Case/Form/Search.php index 37b0b38dbaad..1391393294c2 100644 --- a/CRM/Case/Form/Search.php +++ b/CRM/Case/Form/Search.php @@ -1,9 +1,9 @@ _actionButtonName = $this->getButtonName('next', 'action'); $this->_done = FALSE; - $this->defaults = array(); - /* - * we allow the controller to set force/reset externally, useful when we are being - * driven by the wizard framework - */ - - $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean'); - $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE); - $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'search'); - - $this->assign('context', $this->_context); - - // get user submitted values - // get it from controller only if form has been submitted, else preProcess has set this - if (!empty($_POST) && !$this->controller->isModal()) { - $this->_formValues = $this->controller->exportValues($this->_name); - } - else { - $this->_formValues = $this->get('formValues'); - } - - if (empty($this->_formValues)) { - if (isset($this->_ssID)) { - $this->_formValues = CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID); - } - } + $this->loadStandardSearchOptionsFromUrl(); + $this->loadFormValues(); if ($this->_force) { $this->postProcess(); @@ -174,15 +150,13 @@ public function buildQuickForm() { $this->addRowSelectors($rows); } - $permission = CRM_Core_Permission::getPermission(); - - $tasks = CRM_Case_Task::permissionedTaskTitles($permission); + $tasks = CRM_Case_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission()); if (!empty($this->_formValues['case_deleted'])) { - unset($tasks[1]); + unset($tasks[CRM_Case_Task::TASK_DELETE]); } else { - unset($tasks[4]); + unset($tasks[CRM_Case_Task::RESTORE_CASES]); } $this->addTaskMenu($tasks); @@ -317,7 +291,7 @@ public function postProcess() { * @see valid_date */ public function addRules() { - $this->addFormRule(array('CRM_Case_Form_Search', 'formRule')); + $this->addFormRule(['CRM_Case_Form_Search', 'formRule']); } /** @@ -325,11 +299,13 @@ public function addRules() { * * @param array $fields * Posted values of the form. + * @param array $files + * @param object $form * * @return array|bool */ - public static function formRule($fields) { - $errors = array(); + public static function formRule($fields, $files, $form) { + $errors = []; if (!empty($errors)) { return $errors; @@ -346,7 +322,7 @@ public static function formRule($fields) { * the default array reference */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; $defaults = $this->_formValues; return $defaults; } diff --git a/CRM/Case/Form/Task.php b/CRM/Case/Form/Task.php index d8e93ccfb16a..43d77f1ced67 100644 --- a/CRM/Case/Form/Task.php +++ b/CRM/Case/Form/Task.php @@ -1,9 +1,9 @@ _caseIds = array(); - - $values = $form->controller->exportValues($form->get('searchFormName')); - - $form->_task = $values['task']; - $caseTasks = CRM_Case_Task::tasks(); - $form->assign('taskName', $caseTasks[$form->_task]); - - $ids = array(); - if ($values['radio_ts'] == 'ts_sel') { - foreach ($values as $name => $value) { - if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) { - $ids[] = substr($name, CRM_Core_Form::CB_PREFIX_LEN); - } - } - } - else { - $queryParams = $form->get('queryParams'); - $query = new CRM_Contact_BAO_Query($queryParams, NULL, NULL, FALSE, FALSE, - CRM_Contact_BAO_Query::MODE_CASE - ); - $query->_distinctComponentClause = " ( civicrm_case.id )"; - $query->_groupByComponentClause = " GROUP BY civicrm_case.id "; - $result = $query->searchQuery(0, 0, NULL); - while ($result->fetch()) { - $ids[] = $result->case_id; - } - } - - if (!empty($ids)) { - $form->_componentClause = ' civicrm_case.id IN ( ' . implode(',', $ids) . ' ) '; - $form->assign('totalSelectedCases', count($ids)); - } - - $form->_caseIds = $form->_componentIds = $ids; - - //set the context for redirection for any task actions - $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $form); - $urlParams = 'force=1'; - if (CRM_Utils_Rule::qfKey($qfKey)) { - $urlParams .= "&qfKey=$qfKey"; - } - - $session = CRM_Core_Session::singleton(); - $searchFormName = strtolower($form->get('searchFormName')); - if ($searchFormName == 'search') { - $session->replaceUserContext(CRM_Utils_System::url('civicrm/case/search', $urlParams)); - } - else { - $session->replaceUserContext(CRM_Utils_System::url("civicrm/contact/search/$searchFormName", - $urlParams - )); - } - } + public static $entityShortname = 'case'; /** - * Given the signer id, compute the contact id - * since its used for things like send email + * @inheritDoc */ public function setContactIDs() { - $this->_contactIds = &CRM_Core_DAO::getContactIDsFromComponent($this->_caseIds, - 'civicrm_case_contact' + $this->_contactIds = CRM_Core_DAO::getContactIDsFromComponent($this->_entityIds, + 'civicrm_case_contact', 'case_id' ); } /** - * Simple shell that derived classes can call to add buttons to - * the form with a customized title for the main Submit + * Get the query mode (eg. CRM_Core_BAO_Query::MODE_CASE) * - * @param string $title - * Title of the main button. - * @param string $nextType - * Button type for the form after processing. - * @param string $backType - * @param bool $submitOnce + * @return int */ - public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) { - $this->addButtons(array( - array( - 'type' => $nextType, - 'name' => $title, - 'isDefault' => TRUE, - ), - array( - 'type' => $backType, - 'name' => ts('Cancel'), - ), - ) - ); + public function getQueryMode() { + return CRM_Contact_BAO_Query::MODE_CASE; } } diff --git a/CRM/Case/Form/Task/Batch.php b/CRM/Case/Form/Task/Batch.php new file mode 100644 index 000000000000..c7960774ff1b --- /dev/null +++ b/CRM/Case/Form/Task/Batch.php @@ -0,0 +1,127 @@ +exportValues(); + + if (!isset($params['field'])) { + CRM_Core_Session::setStatus(ts('No updates have been saved.'), ts('Not Saved'), 'alert'); + return; + } + + $customFields = []; + $dateFields = [ + 'case_created_date', + 'case_start_date', + 'case_end_date', + 'case_modified_date', + ]; + foreach ($params['field'] as $key => $value) { + $value['id'] = $key; + + if (!empty($value['case_type'])) { + $caseTypeId = $value['case_type_id'] = $value['case_type'][1]; + } + unset($value['case_type']); + + // Get the case status + $daoClass = 'CRM_Case_DAO_Case'; + $caseStatus = CRM_Utils_Array::value('case_status', $value); + if (!$caseStatus) { + // default to existing status ID + $caseStatus = CRM_Core_DAO::getFieldValue($daoClass, $key, 'status_id'); + } + $value['status_id'] = $caseStatus; + unset($value['case_status']); + + foreach ($dateFields as $val) { + if (isset($value[$val])) { + $value[$val] = CRM_Utils_Date::processDate($value[$val]); + } + } + if (empty($customFields)) { + if (empty($value['case_type_id'])) { + $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $key, 'case_type_id'); + } + + // case type custom data + $customFields = CRM_Core_BAO_CustomField::getFields('Case', FALSE, FALSE, $caseTypeId); + + $customFields = CRM_Utils_Array::crmArrayMerge($customFields, + CRM_Core_BAO_CustomField::getFields('Case', + FALSE, FALSE, NULL, NULL, TRUE + ) + ); + } + //check for custom data + // @todo extract submit functions & + // extend CRM_Event_Form_Task_BatchTest::testSubmit with a data provider to test + // handling of custom data, specifically checkbox fields. + $value['custom'] = CRM_Core_BAO_CustomField::postProcess($params['field'][$key], + $key, + 'Case', + $caseTypeId + ); + + $case = CRM_Case_BAO_Case::add($value); + + // add custom field values + if (!empty($value['custom']) && is_array($value['custom'])) { + CRM_Core_BAO_CustomValueTable::store($value['custom'], 'civicrm_case', $case->id); + } + } + + CRM_Core_Session::setStatus(ts('Your updates have been saved.'), ts('Saved'), 'success'); + } + +} diff --git a/CRM/Case/Form/Task/Delete.php b/CRM/Case/Form/Task/Delete.php index e9701d77c4c3..b963126ccff1 100644 --- a/CRM/Case/Form/Task/Delete.php +++ b/CRM/Case/Form/Task/Delete.php @@ -1,9 +1,9 @@ _caseIds as $caseId) { + foreach ($this->_entityIds as $caseId) { if (CRM_Case_BAO_Case::deleteCase($caseId, $this->_moveToTrash)) { $deleted++; } @@ -84,16 +84,16 @@ public function postProcess() { if ($deleted) { if ($this->_moveToTrash) { - $msg = ts('%count case moved to trash.', array('plural' => '%count cases moved to trash.', 'count' => $deleted)); + $msg = ts('%count case moved to trash.', ['plural' => '%count cases moved to trash.', 'count' => $deleted]); } else { - $msg = ts('%count case permanently deleted.', array('plural' => '%count cases permanently deleted.', 'count' => $deleted)); + $msg = ts('%count case permanently deleted.', ['plural' => '%count cases permanently deleted.', 'count' => $deleted]); } CRM_Core_Session::setStatus($msg, ts('Removed'), 'success'); } if ($failed) { - CRM_Core_Session::setStatus(ts('1 could not be deleted.', array('plural' => '%count could not be deleted.', 'count' => $failed)), ts('Error'), 'error'); + CRM_Core_Session::setStatus(ts('1 could not be deleted.', ['plural' => '%count could not be deleted.', 'count' => $failed]), ts('Error'), 'error'); } } diff --git a/CRM/Case/Form/Task/PDF.php b/CRM/Case/Form/Task/PDF.php index 0a2434bc586b..3afeb6275af9 100644 --- a/CRM/Case/Form/Task/PDF.php +++ b/CRM/Case/Form/Task/PDF.php @@ -1,9 +1,9 @@ skipOnHold = $this->skipDeceased = FALSE; parent::preProcess(); $this->setContactIDs(); - CRM_Contact_Form_Task_PDFLetterCommon::preProcess($this); } /** @@ -86,7 +86,7 @@ public function postProcess() { */ public function listTokens() { $tokens = CRM_Core_SelectValues::contactTokens(); - foreach ($this->_caseIds as $key => $caseId) { + foreach ($this->_entityIds as $key => $caseId) { $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseId, 'case_type_id'); $tokens += CRM_Core_SelectValues::caseTokens($caseTypeId); } diff --git a/CRM/Case/Form/Task/PickProfile.php b/CRM/Case/Form/Task/PickProfile.php new file mode 100644 index 000000000000..c03d48df1e42 --- /dev/null +++ b/CRM/Case/Form/Task/PickProfile.php @@ -0,0 +1,52 @@ +addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Print Case List'), - 'js' => array('onclick' => 'window.print()'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Done'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Print Case List'), + 'js' => ['onclick' => 'window.print()'], + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Done'), + ], + ]); } /** diff --git a/CRM/Case/Form/Task/Restore.php b/CRM/Case/Form/Task/Restore.php index 4318eb1f6612..8257943097c9 100644 --- a/CRM/Case/Form/Task/Restore.php +++ b/CRM/Case/Form/Task/Restore.php @@ -1,9 +1,9 @@ _caseIds as $caseId) { + foreach ($this->_entityIds as $caseId) { if (CRM_Case_BAO_Case::restoreCase($caseId)) { $restoredCases++; } @@ -73,15 +73,15 @@ public function postProcess() { } if ($restoredCases) { - $msg = ts('%count case restored from trash.', array( + $msg = ts('%count case restored from trash.', [ 'plural' => '%count cases restored from trash.', 'count' => $restoredCases, - )); + ]); CRM_Core_Session::setStatus($msg, ts('Restored'), 'success'); } if ($failed) { - CRM_Core_Session::setStatus(ts('1 could not be restored.', array('plural' => '%count could not be restored.', 'count' => $failed)), ts('Error'), 'error'); + CRM_Core_Session::setStatus(ts('1 could not be restored.', ['plural' => '%count could not be restored.', 'count' => $failed]), ts('Error'), 'error'); } } diff --git a/CRM/Case/Form/Task/Result.php b/CRM/Case/Form/Task/Result.php index d4589efabb79..d7a3a4678b48 100644 --- a/CRM/Case/Form/Task/Result.php +++ b/CRM/Case/Form/Task/Result.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Case/Form/Task/SearchTaskHookSample.php b/CRM/Case/Form/Task/SearchTaskHookSample.php index 67b043a2e4e1..ec17a006c2de 100644 --- a/CRM/Case/Form/Task/SearchTaskHookSample.php +++ b/CRM/Case/Form/Task/SearchTaskHookSample.php @@ -1,9 +1,9 @@ _caseIds); + $caseIDs = implode(',', $this->_entityIds); $statusId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'case_status', 'id', 'name'); $query = " SELECT ct.display_name as display_name, @@ -59,11 +59,11 @@ public function preProcess() { $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $rows[] = array( + $rows[] = [ 'display_name' => $dao->display_name, 'start_date' => CRM_Utils_Date::customFormat($dao->start_date), 'status' => $dao->status, - ); + ]; } $this->assign('rows', $rows); } @@ -72,14 +72,13 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Case/Info.php b/CRM/Case/Info.php index 21ef6a32e620..ce5421fd1b0d 100644 --- a/CRM/Case/Info.php +++ b/CRM/Case/Info.php @@ -1,9 +1,9 @@ 'CiviCase', 'translatedName' => ts('CiviCase'), 'title' => ts('CiviCase Engine'), 'search' => 1, 'showActivitiesInCore' => 0, - ); + ]; } /** * @inheritDoc */ public function getAngularModules() { - $result = array(); - $result['crmCaseType'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmCaseType.js'), - 'css' => array('ang/crmCaseType.css'), - 'partials' => array('ang/crmCaseType'), - ); + global $civicrm_root; - CRM_Core_Resources::singleton()->addSetting(array( - 'crmCaseType' => array( - 'REL_TYPE_CNAME' => CRM_Case_XMLProcessor::REL_TYPE_CNAME, - ), - )); + $result = []; + $result['crmCaseType'] = include "$civicrm_root/ang/crmCaseType.ang.php"; return $result; } @@ -98,28 +90,28 @@ public function getManagedEntities() { * @return array */ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { - $permissions = array( - 'delete in CiviCase' => array( + $permissions = [ + 'delete in CiviCase' => [ ts('delete in CiviCase'), ts('Delete cases'), - ), - 'administer CiviCase' => array( + ], + 'administer CiviCase' => [ ts('administer CiviCase'), ts('Define case types, access deleted cases'), - ), - 'access my cases and activities' => array( + ], + 'access my cases and activities' => [ ts('access my cases and activities'), ts('View and edit only those cases managed by this user'), - ), - 'access all cases and activities' => array( + ], + 'access all cases and activities' => [ ts('access all cases and activities'), ts('View and edit all cases (for visible contacts)'), - ), - 'add cases' => array( + ], + 'add cases' => [ ts('add cases'), ts('Open a new case'), - ), - ); + ], + ]; if (!$descriptions) { foreach ($permissions as $name => $attr) { @@ -134,7 +126,7 @@ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = F * @inheritDoc */ public function getReferenceCounts($dao) { - $result = array(); + $result = []; if ($dao instanceof CRM_Core_DAO_OptionValue) { /** @var $dao CRM_Core_DAO_OptionValue */ $activity_type_gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'activity_type', 'id', 'name'); @@ -142,24 +134,30 @@ public function getReferenceCounts($dao) { $count = CRM_Case_XMLRepository::singleton() ->getActivityReferenceCount($dao->name); if ($count > 0) { - $result[] = array( + $result[] = [ 'name' => 'casetypexml:activities', 'type' => 'casetypexml', 'count' => $count, - ); + ]; } } } elseif ($dao instanceof CRM_Contact_DAO_RelationshipType) { /** @var $dao CRM_Contact_DAO_RelationshipType */ - $count = CRM_Case_XMLRepository::singleton() - ->getRelationshipReferenceCount($dao->{CRM_Case_XMLProcessor::REL_TYPE_CNAME}); + + // Need to look both directions, but no need to translate case role + // direction from XML perspective to client-based perspective + $xmlRepo = CRM_Case_XMLRepository::singleton(); + $count = $xmlRepo->getRelationshipReferenceCount($dao->label_a_b); + if ($dao->label_a_b != $dao->label_b_a) { + $count += $xmlRepo->getRelationshipReferenceCount($dao->label_b_a); + } if ($count > 0) { - $result[] = array( + $result[] = [ 'name' => 'casetypexml:relationships', 'type' => 'casetypexml', 'count' => $count, - ); + ]; } } return $result; @@ -170,7 +168,7 @@ public function getReferenceCounts($dao) { * @return array */ public function getUserDashboardElement() { - return array(); + return []; } /** @@ -178,11 +176,19 @@ public function getUserDashboardElement() { * @return array */ public function registerTab() { - return array( + return [ 'title' => ts('Cases'), 'url' => 'case', 'weight' => 50, - ); + ]; + } + + /** + * @inheritDoc + * @return string + */ + public function getIcon() { + return 'crm-i fa-folder-open-o'; } /** @@ -190,10 +196,10 @@ public function registerTab() { * @return array */ public function registerAdvancedSearchPane() { - return array( + return [ 'title' => ts('Cases'), 'weight' => 50, - ); + ]; } /** @@ -214,14 +220,14 @@ public function creatNewShortcut(&$shortCuts) { ) { $activityType = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Open Case'); if ($activityType) { - $shortCuts = array_merge($shortCuts, array( - array( + $shortCuts = array_merge($shortCuts, [ + [ 'path' => 'civicrm/case/add', 'query' => "reset=1&action=add&atype={$activityType}&context=standalone", 'ref' => 'new-case', 'title' => ts('Case'), - ), - )); + ], + ]); } } } @@ -246,7 +252,7 @@ public static function onToggleComponents($oldValue, $newValue, $metadata) { (!$oldValue || !in_array('CiviCase', $oldValue)) ) { $pathToCaseSampleTpl = __DIR__ . '/xml/configuration.sample/'; - CRM_Admin_Form_Setting_Component::loadCaseSampleData($pathToCaseSampleTpl . 'case_sample.mysql.tpl'); + self::loadCaseSampleData($pathToCaseSampleTpl . 'case_sample.mysql.tpl'); if (!CRM_Case_BAO_Case::createCaseViews()) { $msg = ts("Could not create the MySQL views for CiviCase. Your mysql user needs to have the 'CREATE VIEW' permission"); CRM_Core_Error::fatal($msg); @@ -254,4 +260,96 @@ public static function onToggleComponents($oldValue, $newValue, $metadata) { } } + /** + * Load case sample data. + * + * @param string $fileName + * @param bool $lineMode + */ + public static function loadCaseSampleData($fileName, $lineMode = FALSE) { + $dao = new CRM_Core_DAO(); + $db = $dao->getDatabaseConnection(); + + $domain = new CRM_Core_DAO_Domain(); + $domain->find(TRUE); + $multiLingual = (bool) $domain->locales; + $smarty = CRM_Core_Smarty::singleton(); + $smarty->assign('multilingual', $multiLingual); + $smarty->assign('locales', explode(CRM_Core_DAO::VALUE_SEPARATOR, $domain->locales)); + + if (!$lineMode) { + + $string = $smarty->fetch($fileName); + // change \r\n to fix windows issues + $string = str_replace("\r\n", "\n", $string); + + //get rid of comments starting with # and -- + + $string = preg_replace("/^#[^\n]*$/m", "\n", $string); + $string = preg_replace("/^(--[^-]).*/m", "\n", $string); + + $queries = preg_split('/;$/m', $string); + foreach ($queries as $query) { + $query = trim($query); + if (!empty($query)) { + $res = &$db->query($query); + if (PEAR::isError($res)) { + die("Cannot execute $query: " . $res->getMessage()); + } + } + } + } + else { + $fd = fopen($fileName, "r"); + while ($string = fgets($fd)) { + $string = preg_replace("/^#[^\n]*$/m", "\n", $string); + $string = preg_replace("/^(--[^-]).*/m", "\n", $string); + + $string = trim($string); + if (!empty($string)) { + $res = &$db->query($string); + if (PEAR::isError($res)) { + die("Cannot execute $string: " . $res->getMessage()); + } + } + } + } + } + + /** + * @return array + * Array(string $value => string $label). + */ + public static function getRedactOptions() { + return [ + 'default' => ts('Default'), + '0' => ts('Do not redact emails'), + '1' => ts('Redact emails'), + ]; + } + + /** + * @return array + * Array(string $value => string $label). + */ + public static function getMultiClientOptions() { + return [ + 'default' => ts('Default'), + '0' => ts('Single client per case'), + '1' => ts('Multiple client per case'), + ]; + } + + /** + * @return array + * Array(string $value => string $label). + */ + public static function getSortOptions() { + return [ + 'default' => ts('Default'), + '0' => ts('Definition order'), + '1' => ts('Alphabetical order'), + ]; + } + } diff --git a/CRM/Case/ManagedEntities.php b/CRM/Case/ManagedEntities.php index 382f9a05a7b3..be0d846cb169 100644 --- a/CRM/Case/ManagedEntities.php +++ b/CRM/Case/ManagedEntities.php @@ -14,14 +14,14 @@ class CRM_Case_ManagedEntities { * @throws CRM_Core_Exception */ public static function createManagedCaseTypes() { - $entities = array(); + $entities = []; // Use hook_civicrm_caseTypes to build a list of OptionValues // In the long run, we may want more specialized logic for this, but // this design is fairly convenient and will allow us to replace it // without changing the hook_civicrm_caseTypes interface. - $caseTypes = array(); + $caseTypes = []; CRM_Utils_Hook::caseTypes($caseTypes); $proc = new CRM_Case_XMLProcessor(); @@ -32,11 +32,11 @@ public static function createManagedCaseTypes() { } if (isset($caseType['module'], $caseType['name'], $caseType['file'])) { - $entities[] = array( + $entities[] = [ 'module' => $caseType['module'], 'name' => $caseType['name'], 'entity' => 'CaseType', - 'params' => array( + 'params' => [ 'version' => 3, 'name' => $caseType['name'], 'title' => (string) $xml->name, @@ -44,8 +44,8 @@ public static function createManagedCaseTypes() { 'is_reserved' => 1, 'is_active' => 1, 'weight' => $xml->weight ? $xml->weight : 1, - ), - ); + ], + ]; } else { throw new CRM_Core_Exception("Invalid case type"); @@ -64,26 +64,26 @@ public static function createManagedCaseTypes() { * @see CRM_Utils_Hook::managed */ public static function createManagedActivityTypes(CRM_Case_XMLRepository $xmlRepo, CRM_Core_ManagedEntities $me) { - $result = array(); + $result = []; $validActTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, TRUE, 'name'); $actTypes = $xmlRepo->getAllDeclaredActivityTypes(); foreach ($actTypes as $actType) { - $managed = array( + $managed = [ 'module' => 'civicrm', 'name' => "civicase:act:$actType", 'entity' => 'OptionValue', 'update' => 'never', 'cleanup' => 'unused', - 'params' => array( + 'params' => [ 'version' => 3, 'option_group_id' => 'activity_type', 'label' => $actType, 'name' => $actType, 'description' => $actType, 'component_id' => 'CiviCase', - ), - ); + ], + ]; // We'll create managed-entity if this record doesn't exist yet // or if we previously decided to manage this record. @@ -108,35 +108,46 @@ public static function createManagedActivityTypes(CRM_Case_XMLRepository $xmlRep * @see CRM_Utils_Hook::managed */ public static function createManagedRelationshipTypes(CRM_Case_XMLRepository $xmlRepo, CRM_Core_ManagedEntities $me) { - $result = array(); + $result = []; if (!isset(Civi::$statics[__CLASS__]['reltypes'])) { - $relationshipInfo = CRM_Core_PseudoConstant::relationshipType('label', TRUE, NULL); - Civi::$statics[__CLASS__]['reltypes'] = CRM_Utils_Array::collect(CRM_Case_XMLProcessor::REL_TYPE_CNAME, $relationshipInfo); + $relationshipInfo = CRM_Core_PseudoConstant::relationshipType('name', TRUE, NULL); + foreach ($relationshipInfo as $id => $relTypeDetails) { + Civi::$statics[__CLASS__]['reltypes']["{$id}_a_b"] = $relTypeDetails['name_a_b']; + if ($relTypeDetails['name_a_b'] != $relTypeDetails['name_b_a']) { + Civi::$statics[__CLASS__]['reltypes']["{$id}_b_a"] = $relTypeDetails['name_b_a']; + } + } } $validRelTypes = Civi::$statics[__CLASS__]['reltypes']; $relTypes = $xmlRepo->getAllDeclaredRelationshipTypes(); foreach ($relTypes as $relType) { - $managed = array( + // Making assumption that client is the A side of the relationship. + // Relationship label coming from XML, meaning from perspective of + // non-client. + + // These assumptions only apply if a case type is introduced without the + // relationship types already existing. + $managed = [ 'module' => 'civicrm', 'name' => "civicase:rel:$relType", 'entity' => 'RelationshipType', 'update' => 'never', 'cleanup' => 'unused', - 'params' => array( + 'params' => [ 'version' => 3, 'name_a_b' => "$relType is", 'name_b_a' => $relType, 'label_a_b' => "$relType is", 'label_b_a' => $relType, 'description' => $relType, - 'contact_type_a' => 'Individual', - 'contact_type_b' => 'Individual', + 'contact_type_a' => NULL, + 'contact_type_b' => NULL, 'contact_sub_type_a' => NULL, 'contact_sub_type_b' => NULL, - ), - ); + ], + ]; // We'll create managed-entity if this record doesn't exist yet // or if we previously decided to manage this record. diff --git a/CRM/Case/Page/AJAX.php b/CRM/Case/Page/AJAX.php index 4bed5d833c46..4507d161a96b 100644 --- a/CRM/Case/Page/AJAX.php +++ b/CRM/Case/Page/AJAX.php @@ -1,9 +1,9 @@ $caseId, 'entity_table' => 'civicrm_case', - ); + ]; CRM_Core_BAO_EntityTag::del($params); @@ -75,21 +75,21 @@ public function processCaseTags() { $session = CRM_Core_Session::singleton(); - $activityParams = array(); + $activityParams = []; $activityParams['source_contact_id'] = $session->get('userID'); - $activityParams['activity_type_id'] = CRM_Core_OptionGroup::getValue('activity_type', 'Change Case Tags', 'name'); + $activityParams['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Case Tags'); $activityParams['activity_date_time'] = date('YmdHis'); - $activityParams['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); + $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $activityParams['case_id'] = $caseId; $activityParams['is_auto'] = 0; $activityParams['subject'] = 'Change Case Tags'; $activity = CRM_Activity_BAO_Activity::create($activityParams); - $caseParams = array( + $caseParams = [ 'activity_id' => $activity->id, 'case_id' => $caseId, - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); @@ -103,11 +103,11 @@ public function processCaseTags() { public function caseDetails() { $caseId = CRM_Utils_Type::escape($_GET['caseId'], 'Positive'); - $case = civicrm_api3('Case', 'getsingle', array( + $case = civicrm_api3('Case', 'getsingle', [ 'id' => $caseId, 'check_permissions' => TRUE, - 'return' => array('subject', 'case_type_id', 'status_id', 'start_date', 'end_date')) - ); + 'return' => ['subject', 'case_type_id', 'status_id', 'start_date', 'end_date'], + ]); $caseStatuses = CRM_Case_PseudoConstant::caseStatus(); $caseTypes = CRM_Case_PseudoConstant::caseType('title', FALSE); @@ -136,10 +136,10 @@ public function addClient() { CRM_Utils_System::permissionDenied(); } - $params = array( + $params = [ 'case_id' => $caseId, 'contact_id' => $contactId, - ); + ]; CRM_Case_BAO_CaseContact::create($params); @@ -148,21 +148,21 @@ public function addClient() { $session = CRM_Core_Session::singleton(); - $activityParams = array(); + $activityParams = []; $activityParams['source_contact_id'] = $session->get('userID'); - $activityParams['activity_type_id'] = CRM_Core_OptionGroup::getValue('activity_type', 'Add Client To Case', 'name'); + $activityParams['activity_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Add Client To Case'); $activityParams['activity_date_time'] = date('YmdHis'); - $activityParams['status_id'] = CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'); + $activityParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'); $activityParams['case_id'] = $caseId; $activityParams['is_auto'] = 0; $activityParams['subject'] = 'Client Added To Case'; $activity = CRM_Activity_BAO_Activity::create($activityParams); - $caseParams = array( + $caseParams = [ 'activity_id' => $activity->id, 'case_id' => $caseId, - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); CRM_Utils_JSON::output(TRUE); @@ -186,4 +186,30 @@ public static function deleteCaseRoles() { CRM_Utils_System::civiExit(); } + public static function getCases() { + $requiredParameters = [ + 'type' => 'String', + ]; + $optionalParameters = [ + 'case_type_id' => 'CommaSeparatedIntegers', + 'status_id' => 'CommaSeparatedIntegers', + 'all' => 'Positive', + ]; + $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams(); + $params += CRM_Core_Page_AJAX::validateParams($requiredParameters, $optionalParameters); + + $allCases = (bool) $params['all']; + + $cases = CRM_Case_BAO_Case::getCases($allCases, $params); + + $casesDT = [ + 'recordsFiltered' => $cases['total'], + 'recordsTotal' => $cases['total'], + ]; + unset($cases['total']); + $casesDT['data'] = array_values($cases); + + CRM_Utils_JSON::output($casesDT); + } + } diff --git a/CRM/Case/Page/CaseDetails.php b/CRM/Case/Page/CaseDetails.php index 61f673f87b10..77b09b57be2a 100644 --- a/CRM/Case/Page/CaseDetails.php +++ b/CRM/Case/Page/CaseDetails.php @@ -1,9 +1,9 @@ _action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $this->assign('action', $this->_action); $this->assign('context', $this->_context); diff --git a/CRM/Case/Page/DashBoard.php b/CRM/Case/Page/DashBoard.php index da548c77111b..fd0002c3cffe 100644 --- a/CRM/Case/Page/DashBoard.php +++ b/CRM/Case/Page/DashBoard.php @@ -1,9 +1,9 @@ assign('all', $allCases); if (!$allCases) { $this->assign('myCases', TRUE); } @@ -82,22 +83,27 @@ public function preProcess() { ) { $this->assign('newClient', TRUE); } - $summary = CRM_Case_BAO_Case::getCasesSummary($allCases, $userID); - $upcoming = CRM_Case_BAO_Case::getCases($allCases, $userID, 'upcoming'); - $recent = CRM_Case_BAO_Case::getCases($allCases, $userID, 'recent'); + $summary = CRM_Case_BAO_Case::getCasesSummary($allCases); + $upcoming = CRM_Case_BAO_Case::getCases($allCases, [], 'dashboard', TRUE); + $recent = CRM_Case_BAO_Case::getCases($allCases, ['type' => 'recent'], 'dashboard', TRUE); - foreach ($upcoming as $key => $value) { - if (strtotime($value['case_scheduled_activity_date']) < time()) { - $upcoming[$key]['activity_status'] = 'status-overdue'; - } - } $this->assign('casesSummary', $summary); if (!empty($upcoming)) { - $this->assign('upcomingCases', $upcoming); + $this->assign('upcomingCases', TRUE); } if (!empty($recent)) { - $this->assign('recentCases', $recent); + $this->assign('recentCases', TRUE); } + + $controller = new CRM_Core_Controller_Simple('CRM_Case_Form_Search', + ts('Case'), CRM_Core_Action::BROWSE, + NULL, + FALSE, FALSE, TRUE + ); + $controller->set('context', 'dashboard'); + $controller->setEmbedded(TRUE); + $controller->process(); + $controller->run(); } /** diff --git a/CRM/Case/Page/Tab.php b/CRM/Case/Page/Tab.php index 64599e6a7cde..d7c7055ca2c4 100644 --- a/CRM/Case/Page/Tab.php +++ b/CRM/Case/Page/Tab.php @@ -1,9 +1,9 @@ _id = CRM_Utils_Request::retrieve('id', 'Positive', $this); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); if ($this->_contactId) { $this->assign('contactId', $this->_contactId); @@ -67,8 +67,7 @@ public function preProcess() { if ($this->_id && ($this->_action & CRM_Core_Action::VIEW)) { //user might have special permissions to view this case, CRM-5666 if (!CRM_Core_Permission::check('access all cases and activities')) { - $session = CRM_Core_Session::singleton(); - $userCases = CRM_Case_BAO_Case::getCases(FALSE, $session->get('userID'), 'any'); + $userCases = CRM_Case_BAO_Case::getCases(FALSE, ['type' => 'any']); if (!array_key_exists($this->_id, $userCases)) { CRM_Core_Error::fatal(ts('You are not authorized to access this page.')); } @@ -179,7 +178,7 @@ public function edit() { */ public function run() { $contactID = CRM_Utils_Request::retrieve('cid', 'Positive', CRM_Core_DAO::$_nullArray); - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); if ($context == 'standalone' && !$contactID) { $this->_action = CRM_Core_Action::ADD; @@ -217,26 +216,26 @@ public function run() { * @return array * (reference) of action links */ - static public function &links() { + public static function &links() { $config = CRM_Core_Config::singleton(); if (!(self::$_links)) { $deleteExtra = ts('Are you sure you want to delete this case?'); - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('Manage'), 'url' => 'civicrm/contact/view/case', 'qs' => 'action=view&reset=1&cid=%%cid%%&id=%%id%%', 'class' => 'no-popup', 'title' => ts('Manage Case'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/case', 'qs' => 'action=delete&reset=1&cid=%%cid%%&id=%%id%%', 'title' => ts('Delete Case'), - ), - ); + ], + ]; } return self::$_links; } diff --git a/CRM/Case/PseudoConstant.php b/CRM/Case/PseudoConstant.php index 4fde3b50e751..a1bb966de8f8 100644 --- a/CRM/Case/PseudoConstant.php +++ b/CRM/Case/PseudoConstant.php @@ -1,9 +1,9 @@ fetch()) { if ($indexName) { $index = $dao->name; @@ -187,16 +181,16 @@ public static function &caseActivityType($indexName = TRUE, $all = FALSE) { else { $index = $dao->value; } - $activityTypes[$index] = array(); + $activityTypes[$index] = []; $activityTypes[$index]['id'] = $dao->value; $activityTypes[$index]['label'] = $dao->label; $activityTypes[$index]['name'] = $dao->name; $activityTypes[$index]['icon'] = $dao->icon; $activityTypes[$index]['description'] = $dao->description; } - self::$activityTypeList[$cache] = $activityTypes; + Civi::$statics[__CLASS__]['activityTypeList'][$cache] = $activityTypes; } - return self::$activityTypeList[$cache]; + return Civi::$statics[__CLASS__]['activityTypeList'][$cache]; } /** diff --git a/CRM/Case/Selector/Search.php b/CRM/Case/Selector/Search.php index 990503e5c1b9..c1a4e6853661 100644 --- a/CRM/Case/Selector/Search.php +++ b/CRM/Case/Selector/Search.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::RENEW => [ 'name' => ts('Restore'), 'url' => 'civicrm/contact/view/case', 'qs' => 'reset=1&action=renew&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams, 'ref' => 'restore-case', 'title' => ts('Restore Case'), - ), - ); + ], + ]; } else { - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('Manage'), 'url' => 'civicrm/contact/view/case', 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=view&context=%%cxt%%&selectedChild=case' . $extraParams, 'ref' => 'manage-case', 'class' => 'no-popup', 'title' => ts('Manage Case'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/case', 'qs' => 'reset=1&action=delete&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams, 'ref' => 'delete-case', 'title' => ts('Delete Case'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Assign to Another Client'), 'url' => 'civicrm/contact/view/case/editClient', 'qs' => 'reset=1&action=update&id=%%id%%&cid=%%cid%%&context=%%cxt%%' . $extraParams, 'ref' => 'reassign', 'class' => 'medium-popup', 'title' => ts('Assign to Another Client'), - ), - ); + ], + ]; } - $actionLinks = array(); + $actionLinks = []; foreach (self::$_links as $key => $value) { $actionLinks['primaryActions'][$key] = $value; } @@ -292,10 +292,10 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $this->_additionalClause ); // process the result of the query - $rows = array(); + $rows = []; //CRM-4418 check for view, edit, delete - $permissions = array(CRM_Core_Permission::VIEW); + $permissions = [CRM_Core_Permission::VIEW]; if (CRM_Core_Permission::check('access all cases and activities') || CRM_Core_Permission::check('access my cases and activities') ) { @@ -308,10 +308,10 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $caseStatus = CRM_Core_OptionGroup::values('case_status', FALSE, FALSE, FALSE, " AND v.name = 'Urgent' "); - $scheduledInfo = array(); + $scheduledInfo = []; while ($result->fetch()) { - $row = array(); + $row = []; // the columns we are interested in foreach (self::$_properties as $property) { if (isset($result->$property)) { @@ -322,7 +322,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $isDeleted = FALSE; if ($result->case_deleted) { $isDeleted = TRUE; - $row['case_status_id'] = empty($row['case_status_id']) ? "" : $row['case_status_id'] . '
    (deleted)'; + $row['case_status_id'] = empty($row['case_status_id']) ? "" : $row['case_status_id'] . '
    ' . ts('(deleted)'); } $scheduledInfo['case_id'][] = $result->case_id; @@ -332,11 +332,11 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $links = self::links($isDeleted, $this->_key); $row['action'] = CRM_Core_Action::formLink($links['primaryActions'], - $mask, array( + $mask, [ 'id' => $result->case_id, 'cid' => $result->contact_id, 'cxt' => $this->_context, - ), + ], ts('more'), FALSE, 'case.selector.actions', @@ -349,12 +349,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { //adding case manager to case selector.CRM-4510. $caseType = CRM_Case_BAO_Case::getCaseType($result->case_id, 'name'); - $caseManagerContact = CRM_Case_BAO_Case::getCaseManagerContact($caseType, $result->case_id); - - if (!empty($caseManagerContact)) { - $row['casemanager_id'] = CRM_Utils_Array::value('casemanager_id', $caseManagerContact); - $row['casemanager'] = CRM_Utils_Array::value('casemanager', $caseManagerContact); - } + $row['casemanager'] = CRM_Case_BAO_Case::getCaseManagerContact($caseType, $result->case_id); if (isset($result->case_status_id) && array_key_exists($result->case_status_id, $caseStatus) @@ -405,51 +400,51 @@ public function getQILL() { */ public function &getColumnHeaders($action = NULL, $output = NULL) { if (!isset(self::$_columnHeaders)) { - self::$_columnHeaders = array( - array( + self::$_columnHeaders = [ + [ 'name' => ts('Subject'), 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Status'), 'sort' => 'case_status', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Case Type'), 'sort' => 'case_type', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('My Role'), 'sort' => 'case_role', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Case Manager'), 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Most Recent'), 'sort' => 'case_recent_activity_date', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Next Sched.'), 'sort' => 'case_scheduled_activity_date', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array('name' => ts('Actions')), - ); + ], + ['name' => ts('Actions')], + ]; if (!$this->_single) { - $pre = array( - array( + $pre = [ + [ 'name' => ts('Client'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - ); + ], + ]; self::$_columnHeaders = array_merge($pre, self::$_columnHeaders); } @@ -461,7 +456,7 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { * @return mixed */ public function alphabetQuery() { - return $this->_query->searchQuery(NULL, NULL, NULL, FALSE, FALSE, TRUE); + return $this->_query->alphabetQuery(); } /** @@ -484,4 +479,77 @@ public function getExportFileName($output = 'csv') { return ts('Case Search'); } + /** + * Add the set of "actionLinks" to the case activity + * + * @param int $caseID + * @param int $contactID + * @param int $userID + * @param string $context + * @param \CRM_Core_DAO $dao + * + * @return string + * HTML formatted Link + */ + public static function addCaseActivityLinks($caseID, $contactID, $userID, $context, $dao) { + // FIXME: Why are we not using CRM_Core_Action for these links? This is too much manual work and likely to get out-of-sync with core markup. + $caseActivityId = $dao->id; + $allowView = CRM_Case_BAO_Case::checkPermission($caseActivityId, 'view', $dao->activity_type_id, $userID); + $allowEdit = CRM_Case_BAO_Case::checkPermission($caseActivityId, 'edit', $dao->activity_type_id, $userID); + $allowDelete = CRM_Case_BAO_Case::checkPermission($caseActivityId, 'delete', $dao->activity_type_id, $userID); + $emailActivityTypeIDs = [ + 'Email' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email'), + 'Inbound Email' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Inbound Email'), + ]; + $url = CRM_Utils_System::url("civicrm/case/activity", + "reset=1&cid={$contactID}&caseid={$caseID}", FALSE, NULL, FALSE + ); + $contextUrl = ''; + if ($context == 'fulltext') { + $contextUrl = "&context={$context}"; + } + $editUrl = "{$url}&action=update{$contextUrl}"; + $deleteUrl = "{$url}&action=delete{$contextUrl}"; + $restoreUrl = "{$url}&action=renew{$contextUrl}"; + $viewTitle = ts('View activity'); + $caseDeleted = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $caseID, 'is_deleted'); + + $url = ""; + $css = 'class="action-item crm-hover-button"'; + if ($allowView) { + $viewUrl = CRM_Utils_System::url('civicrm/case/activity/view', array('cid' => $contactID, 'aid' => $caseActivityId)); + $url = '' . ts('View') . ''; + } + $additionalUrl = "&id={$caseActivityId}"; + if (!$dao->deleted) { + //hide edit link of activity type email.CRM-4530. + if (!in_array($dao->type, $emailActivityTypeIDs)) { + //hide Edit link if activity type is NOT editable (special case activities).CRM-5871 + if ($allowEdit) { + $url .= '' . ts('Edit') . ' '; + } + } + if ($allowDelete) { + $url .= ' ' . ts('Delete') . ''; + } + } + elseif (!$caseDeleted) { + $url = ' ' . ts('Restore') . ''; + } + + //check for operations. + if (CRM_Case_BAO_Case::checkPermission($caseActivityId, 'Move To Case', $dao->activity_type_id)) { + $url .= ' ' . ts('Move To Case') . ' '; + } + if (CRM_Case_BAO_Case::checkPermission($caseActivityId, 'Copy To Case', $dao->activity_type_id)) { + $url .= ' ' . ts('Copy To Case') . ' '; + } + // if there are file attachments we will return how many and, if only one, add a link to it + if (!empty($dao->attachment_ids)) { + $url .= implode(' ', CRM_Core_BAO_File::paperIconAttachment('civicrm_activity', $caseActivityId)); + } + + return $url; + } + } diff --git a/CRM/Case/StateMachine/Search.php b/CRM/Case/StateMachine/Search.php index 0cfa120af760..8f29c530ad4a 100644 --- a/CRM/Case/StateMachine/Search.php +++ b/CRM/Case/StateMachine/Search.php @@ -1,9 +1,9 @@ _pages = array(); + $this->_pages = []; $this->_pages['CRM_Case_Form_Search'] = NULL; list($task, $result) = $this->taskName($controller, 'Search'); @@ -80,7 +80,7 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { * * @param string $formName * - * @return string + * @return array * the name of the form that will handle the task */ public function taskName($controller, $formName = 'Search') { diff --git a/CRM/Case/Task.php b/CRM/Case/Task.php index 5af2aaaf1c9d..5907da813d54 100644 --- a/CRM/Case/Task.php +++ b/CRM/Case/Task.php @@ -1,9 +1,9 @@ array( + self::$_tasks = [ + self::TASK_DELETE => [ 'title' => ts('Delete cases'), 'class' => 'CRM_Case_Form_Task_Delete', 'result' => FALSE, - ), - 2 => array( + ], + self::TASK_PRINT => [ 'title' => ts('Print selected rows'), 'class' => 'CRM_Case_Form_Task_Print', 'result' => FALSE, - ), - 3 => array( + ], + self::TASK_EXPORT => [ 'title' => ts('Export cases'), - 'class' => array( - 'CRM_Export_Form_Select', + 'class' => [ + 'CRM_Export_Form_Select_Case', 'CRM_Export_Form_Map', - ), + ], 'result' => FALSE, - ), - 4 => array( + ], + self::RESTORE_CASES => [ 'title' => ts('Restore cases'), 'class' => 'CRM_Case_Form_Task_Restore', 'result' => FALSE, - ), - 5 => array( - 'title' => ts('Print/merge Document'), + ], + self::PDF_LETTER => [ + 'title' => ts('Print/merge document'), 'class' => 'CRM_Case_Form_Task_PDF', 'result' => FALSE, - ), - ); + ], + self::BATCH_UPDATE => [ + 'title' => ts('Update multiple cases'), + 'class' => [ + 'CRM_Case_Form_Task_PickProfile', + 'CRM_Case_Form_Task_Batch', + ], + 'result' => FALSE, + ], + ]; //CRM-4418, check for delete if (!CRM_Core_Permission::check('delete in CiviCase')) { - unset(self::$_tasks[1]); + unset(self::$_tasks[self::TASK_DELETE]); } - CRM_Utils_Hook::searchTasks('case', self::$_tasks); - asort(self::$_tasks); + parent::tasks(); } return self::$_tasks; } - /** - * These tasks are the core set of task titles. - * - * @return array - * the set of task titles - */ - public static function &taskTitles() { - self::tasks(); - $titles = array(); - foreach (self::$_tasks as $id => $value) { - $titles[$id] = $value['title']; - } - return $titles; - } - - /** - * These tasks get added based on the context the user is in. - * - * @return array - * the set of optional tasks for a group of contacts - */ - public static function &optionalTaskTitle() { - $tasks = array(); - return $tasks; - } - /** * Show tasks selectively based on the permission level. * of the user * * @param int $permission + * @param array $params * * @return array * set of tasks that are valid for the user */ - public static function &permissionedTaskTitles($permission) { - $tasks = array(); + public static function permissionedTaskTitles($permission, $params = []) { if (($permission == CRM_Core_Permission::EDIT) || CRM_Core_Permission::check('access all cases and activities') || CRM_Core_Permission::check('access my cases and activities') @@ -149,14 +125,16 @@ public static function &permissionedTaskTitles($permission) { $tasks = self::taskTitles(); } else { - $tasks = array( - 3 => self::$_tasks[3]['title'], - ); + $tasks = [ + self::TASK_EXPORT => self::$_tasks[self::TASK_EXPORT]['title'], + ]; //CRM-4418, if (CRM_Core_Permission::check('delete in CiviCase')) { - $tasks[1] = self::$_tasks[1]['title']; + $tasks[self::TASK_DELETE] = self::$_tasks[self::TASK_DELETE]['title']; } } + + $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params); return $tasks; } @@ -172,13 +150,13 @@ public static function getTask($value) { self::tasks(); if (!$value || !CRM_Utils_Array::value($value, self::$_tasks)) { // make the print task by default - $value = 2; + $value = self::TASK_PRINT; } - return array( + return [ self::$_tasks[$value]['class'], self::$_tasks[$value]['result'], - ); + ]; } } diff --git a/CRM/Case/XMLProcessor.php b/CRM/Case/XMLProcessor.php index e1357b9fa24f..577b8610c48e 100644 --- a/CRM/Case/XMLProcessor.php +++ b/CRM/Case/XMLProcessor.php @@ -1,9 +1,9 @@ string $relTypeCname) - */ - public static $activityTypes = NULL; - - /** - * FIXME: This does *NOT* belong in a static property, but we're too late in - * the 4.5-cycle to do the necessary cleanup. + * Format is [int $id => string $relTypeCname]. * - * @var array|null array(int $id => string $relTypeCname) + * @var array|null */ - public static $relationshipTypes = NULL; - - /** - * Relationship-types have four name fields (name_a_b, name_b_a, label_a_b, - * label_b_a), but CiviCase XML refers to reltypes by a single name. - * REL_TYPE_CNAME identifies the canonical name field as used by CiviCase XML. - * - * This appears to be "label_b_a", but IMHO "name_b_a" would be more - * sensible. - */ - const REL_TYPE_CNAME = 'label_b_a'; + public static $activityTypes = NULL; /** * @param $caseType @@ -107,19 +91,44 @@ public function &allActivityTypes($indexName = TRUE, $all = FALSE) { } /** + * Get all relationship type display labels (not machine names) + * + * @param bool $fromXML + * TODO: This parameter is always FALSE now so no longer needed. + * Is this to be used for lookup of values from XML? + * Relationships are recorded in XML from the perspective of the non-client + * while relationships in the UI and everywhere else are from the + * perspective of the client. Since the XML can't be expected to be + * switched, the direction needs to be translated. * @return array */ - public function &allRelationshipTypes() { - if (self::$relationshipTypes === NULL) { - $relationshipInfo = CRM_Core_PseudoConstant::relationshipType('label', TRUE); + public function &allRelationshipTypes($fromXML = FALSE) { + if (!isset(Civi::$statics[__CLASS__]['reltypes'][$fromXML])) { + // Note this now includes disabled types too. The only place this + // function is being used is for comparison against a list, not + // displaying a dropdown list or something like that, so we need + // to include disabled. + $relationshipInfo = civicrm_api3('RelationshipType', 'get', [ + 'options' => ['limit' => 0], + ]); - self::$relationshipTypes = array(); - foreach ($relationshipInfo as $id => $info) { - self::$relationshipTypes[$id] = $info[CRM_Case_XMLProcessor::REL_TYPE_CNAME]; + Civi::$statics[__CLASS__]['reltypes'][$fromXML] = []; + foreach ($relationshipInfo['values'] as $id => $info) { + Civi::$statics[__CLASS__]['reltypes'][$fromXML][$id . '_b_a'] = ($fromXML) ? $info['label_a_b'] : $info['label_b_a']; + /** + * Exclude if bidirectional + * (Why? I'm thinking this was for consistency with the dropdown + * in ang/crmCaseType.js where it would be needed to avoid seeing + * duplicates in the dropdown. Not sure if needed here but keeping + * as-is.) + */ + if ($info['label_b_a'] !== $info['label_a_b']) { + Civi::$statics[__CLASS__]['reltypes'][$fromXML][$id . '_a_b'] = ($fromXML) ? $info['label_b_a'] : $info['label_a_b']; + } } } - return self::$relationshipTypes; + return Civi::$statics[__CLASS__]['reltypes'][$fromXML]; } /** @@ -127,7 +136,7 @@ public function &allRelationshipTypes() { */ public static function flushStaticCaches() { self::$activityTypes = NULL; - self::$relationshipTypes = NULL; + unset(Civi::$statics[__CLASS__]['reltypes']); } } diff --git a/CRM/Case/XMLProcessor/Process.php b/CRM/Case/XMLProcessor/Process.php index fe1469bfacc0..841d630a133b 100644 --- a/CRM/Case/XMLProcessor/Process.php +++ b/CRM/Case/XMLProcessor/Process.php @@ -1,9 +1,9 @@ $caseType, 2 => $docLink) + [1 => $caseType, 2 => $docLink] )); return FALSE; } @@ -71,7 +73,7 @@ public function get($caseType, $fieldSet, $isLabel = FALSE, $maskAction = FALSE) if ($xml === FALSE) { $docLink = CRM_Utils_System::docURL2("user/case-management/set-up"); CRM_Core_Error::fatal(ts("Unable to load configuration file for the referenced case type: '%1' %2.", - array(1 => $caseType, 2 => $docLink) + [1 => $caseType, 2 => $docLink] )); return FALSE; } @@ -97,14 +99,13 @@ public function get($caseType, $fieldSet, $isLabel = FALSE, $maskAction = FALSE) public function process($xml, &$params) { $standardTimeline = CRM_Utils_Array::value('standardTimeline', $params); $activitySetName = CRM_Utils_Array::value('activitySetName', $params); - $activityTypeName = CRM_Utils_Array::value('activityTypeName', $params); if ('Open Case' == CRM_Utils_Array::value('activityTypeName', $params)) { // create relationships for the ones that are required foreach ($xml->CaseRoles as $caseRoleXML) { foreach ($caseRoleXML->RelationshipType as $relationshipTypeXML) { if ((int ) $relationshipTypeXML->creator == 1) { - if (!$this->createRelationships((string ) $relationshipTypeXML->name, + if (!$this->createRelationships($relationshipTypeXML, $params ) ) { @@ -180,23 +181,23 @@ public function processActivitySet($activitySetXML, &$params) { * @return array|mixed */ public function &caseRoles($caseRolesXML, $isCaseManager = FALSE) { - $relationshipTypes = &$this->allRelationshipTypes(); + // Look up relationship types according to the XML convention (described + // from perspective of non-client) but return the labels according to the UI + // convention (described from perspective of client) + $relationshipTypesToReturn = &$this->allRelationshipTypes(FALSE); - $result = array(); + $result = []; foreach ($caseRolesXML as $caseRoleXML) { foreach ($caseRoleXML->RelationshipType as $relationshipTypeXML) { - $relationshipTypeName = (string ) $relationshipTypeXML->name; - $relationshipTypeID = array_search($relationshipTypeName, - $relationshipTypes - ); + list($relationshipTypeID,) = $this->locateNameOrLabel($relationshipTypeXML); if ($relationshipTypeID === FALSE) { continue; } if (!$isCaseManager) { - $result[$relationshipTypeID] = $relationshipTypeName; + $result[$relationshipTypeID] = $relationshipTypesToReturn[$relationshipTypeID]; } - elseif ($relationshipTypeXML->manager) { + elseif ($relationshipTypeXML->manager == 1) { return $relationshipTypeID; } } @@ -205,39 +206,46 @@ public function &caseRoles($caseRolesXML, $isCaseManager = FALSE) { } /** - * @param string $relationshipTypeName + * @param SimpleXMLElement $relationshipTypeXML * @param array $params * * @return bool * @throws Exception */ - public function createRelationships($relationshipTypeName, &$params) { - $relationshipTypes = &$this->allRelationshipTypes(); - // get the relationship id - $relationshipTypeID = array_search($relationshipTypeName, $relationshipTypes); + public function createRelationships($relationshipTypeXML, &$params) { - if ($relationshipTypeID === FALSE) { + // get the relationship + list($relationshipType, $relationshipTypeName) = $this->locateNameOrLabel($relationshipTypeXML); + if ($relationshipType === FALSE) { $docLink = CRM_Utils_System::docURL2("user/case-management/set-up"); CRM_Core_Error::fatal(ts('Relationship type %1, found in case configuration file, is not present in the database %2', - array(1 => $relationshipTypeName, 2 => $docLink) + [1 => $relationshipTypeName, 2 => $docLink] )); return FALSE; } $client = $params['clientID']; if (!is_array($client)) { - $client = array($client); + $client = [$client]; } foreach ($client as $key => $clientId) { - $relationshipParams = array( - 'relationship_type_id' => $relationshipTypeID, - 'contact_id_a' => $clientId, - 'contact_id_b' => $params['creatorID'], + $relationshipParams = [ + 'relationship_type_id' => substr($relationshipType, 0, -4), 'is_active' => 1, 'case_id' => $params['caseID'], 'start_date' => date("Ymd"), - ); + 'end_date' => CRM_Utils_Array::value('relationship_end_date', $params), + ]; + + if (substr($relationshipType, -4) == '_b_a') { + $relationshipParams['contact_id_b'] = $clientId; + $relationshipParams['contact_id_a'] = $params['creatorID']; + } + if (substr($relationshipType, -4) == '_a_b') { + $relationshipParams['contact_id_a'] = $clientId; + $relationshipParams['contact_id_b'] = $params['creatorID']; + } if (!$this->createRelationship($relationshipParams)) { CRM_Core_Error::fatal(); @@ -272,7 +280,7 @@ public function createRelationship(&$params) { */ public function activityTypes($activityTypesXML, $maxInst = FALSE, $isLabel = FALSE, $maskAction = FALSE) { $activityTypes = &$this->allActivityTypes(TRUE, TRUE); - $result = array(); + $result = []; foreach ($activityTypesXML as $activityTypeXML) { foreach ($activityTypeXML as $recordXML) { $activityTypeName = (string ) $recordXML->name; @@ -313,10 +321,11 @@ public function activityTypes($activityTypesXML, $maxInst = FALSE, $isLabel = FA /** * @param SimpleXMLElement $caseTypeXML + * * @return array symbolic activity-type names */ public function getDeclaredActivityTypes($caseTypeXML) { - $result = array(); + $result = []; if (!empty($caseTypeXML->ActivityTypes) && $caseTypeXML->ActivityTypes->ActivityType) { foreach ($caseTypeXML->ActivityTypes->ActivityType as $activityTypeXML) { @@ -340,15 +349,19 @@ public function getDeclaredActivityTypes($caseTypeXML) { } /** + * Relationships are straight from XML, described from perspective of non-client + * * @param SimpleXMLElement $caseTypeXML + * * @return array symbolic relationship-type names */ public function getDeclaredRelationshipTypes($caseTypeXML) { - $result = array(); + $result = []; if (!empty($caseTypeXML->CaseRoles) && $caseTypeXML->CaseRoles->RelationshipType) { foreach ($caseTypeXML->CaseRoles->RelationshipType as $relTypeXML) { - $result[] = (string) $relTypeXML->name; + list(, $relationshipTypeMachineName) = $this->locateNameOrLabel($relTypeXML); + $result[] = $relationshipTypeMachineName; } } @@ -361,7 +374,7 @@ public function getDeclaredRelationshipTypes($caseTypeXML) { * @param array $params */ public function deleteEmptyActivity(&$params) { - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $query = " @@ -375,7 +388,7 @@ public function deleteEmptyActivity(&$params) { AND a.is_current_revision = 1 AND ca.case_id = %2 "; - $sqlParams = array(1 => array($params['clientID'], 'Integer'), 2 => array($params['caseID'], 'Integer')); + $sqlParams = [1 => [$params['clientID'], 'Integer'], 2 => [$params['caseID'], 'Integer']]; CRM_Core_DAO::executeQuery($query, $sqlParams); } @@ -394,10 +407,10 @@ public function isActivityPresent(&$params) { AND a.is_deleted = 0 "; - $sqlParams = array( - 1 => array($params['activityTypeID'], 'Integer'), - 2 => array($params['caseID'], 'Integer'), - ); + $sqlParams = [ + 1 => [$params['activityTypeID'], 'Integer'], + 2 => [$params['caseID'], 'Integer'], + ]; $count = CRM_Core_DAO::singleValueQuery($query, $sqlParams); // check for max instance @@ -423,7 +436,7 @@ public function createActivity($activityTypeXML, &$params) { if (!$activityTypeInfo) { $docLink = CRM_Utils_System::docURL2("user/case-management/set-up"); CRM_Core_Error::fatal(ts('Activity type %1, found in case configuration file, is not present in the database %2', - array(1 => $activityTypeName, 2 => $docLink) + [1 => $activityTypeName, 2 => $docLink] )); return FALSE; } @@ -446,7 +459,7 @@ public function createActivity($activityTypeXML, &$params) { } if ($activityTypeName == 'Open Case') { - $activityParams = array( + $activityParams = [ 'activity_type_id' => $activityTypeID, 'source_contact_id' => $params['creatorID'], 'is_auto' => FALSE, @@ -459,10 +472,10 @@ public function createActivity($activityTypeXML, &$params) { 'details' => CRM_Utils_Array::value('details', $params), 'duration' => CRM_Utils_Array::value('duration', $params), 'weight' => $orderVal, - ); + ]; } else { - $activityParams = array( + $activityParams = [ 'activity_type_id' => $activityTypeID, 'source_contact_id' => $params['creatorID'], 'is_auto' => TRUE, @@ -470,9 +483,11 @@ public function createActivity($activityTypeXML, &$params) { 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', $statusName), 'target_contact_id' => $client, 'weight' => $orderVal, - ); + ]; } + $activityParams['assignee_contact_id'] = $this->getDefaultAssigneeForActivity($activityParams, $activityTypeXML); + //parsing date to default preference format $params['activity_date_time'] = CRM_Utils_Date::processDate($params['activity_date_time']); @@ -506,7 +521,7 @@ public function createActivity($activityTypeXML, &$params) { else { $referenceActivityInfo = CRM_Utils_Array::value($referenceActivityName, $activityTypes); if ($referenceActivityInfo['id']) { - $caseActivityParams = array('activity_type_id' => $referenceActivityInfo['id']); + $caseActivityParams = ['activity_type_id' => $referenceActivityInfo['id']]; //if reference_select is set take according activity. if ($referenceSelect = (string) $activityTypeXML->reference_select) { @@ -559,21 +574,170 @@ public function createActivity($activityTypeXML, &$params) { } // create case activity record - $caseParams = array( + $caseParams = [ 'activity_id' => $activity->id, 'case_id' => $params['caseID'], - ); + ]; CRM_Case_BAO_Case::processCaseActivity($caseParams); return TRUE; } + /** + * Return the default assignee contact for the activity. + * + * @param array $activityParams + * @param object $activityTypeXML + * + * @return int|null the ID of the default assignee contact or null if none. + */ + protected function getDefaultAssigneeForActivity($activityParams, $activityTypeXML) { + if (!isset($activityTypeXML->default_assignee_type)) { + return NULL; + } + + $defaultAssigneeOptionsValues = $this->getDefaultAssigneeOptionValues(); + + switch ($activityTypeXML->default_assignee_type) { + case $defaultAssigneeOptionsValues['BY_RELATIONSHIP']: + return $this->getDefaultAssigneeByRelationship($activityParams, $activityTypeXML); + + break; + case $defaultAssigneeOptionsValues['SPECIFIC_CONTACT']: + return $this->getDefaultAssigneeBySpecificContact($activityTypeXML); + + break; + case $defaultAssigneeOptionsValues['USER_CREATING_THE_CASE']: + return $activityParams['source_contact_id']; + + break; + case $defaultAssigneeOptionsValues['NONE']: + default: + return NULL; + } + } + + /** + * Fetches and caches the activity's default assignee options. + * + * @return array + */ + protected function getDefaultAssigneeOptionValues() { + if (!empty($this->defaultAssigneeOptionsValues)) { + return $this->defaultAssigneeOptionsValues; + } + + $defaultAssigneeOptions = civicrm_api3('OptionValue', 'get', [ + 'option_group_id' => 'activity_default_assignee', + 'options' => ['limit' => 0], + ]); + + foreach ($defaultAssigneeOptions['values'] as $option) { + $this->defaultAssigneeOptionsValues[$option['name']] = $option['value']; + } + + return $this->defaultAssigneeOptionsValues; + } + + /** + * Returns the default assignee for the activity by searching for the target's + * contact relationship type defined in the activity's details. + * + * @param array $activityParams + * @param object $activityTypeXML + * + * @return int|null the ID of the default assignee contact or null if none. + */ + protected function getDefaultAssigneeByRelationship($activityParams, $activityTypeXML) { + $isDefaultRelationshipDefined = isset($activityTypeXML->default_assignee_relationship) + && preg_match('/\d+_[ab]_[ab]/', $activityTypeXML->default_assignee_relationship); + + if (!$isDefaultRelationshipDefined) { + return NULL; + } + + $targetContactId = is_array($activityParams['target_contact_id']) + ? CRM_Utils_Array::first($activityParams['target_contact_id']) + : $activityParams['target_contact_id']; + list($relTypeId, $a, $b) = explode('_', $activityTypeXML->default_assignee_relationship); + + $params = [ + 'relationship_type_id' => $relTypeId, + "contact_id_$b" => $targetContactId, + 'is_active' => 1, + ]; + + if ($this->isBidirectionalRelationshipType($relTypeId)) { + $params["contact_id_$a"] = $targetContactId; + $params['options']['or'] = [['contact_id_a', 'contact_id_b']]; + } + + $relationships = civicrm_api3('Relationship', 'get', $params); + + if ($relationships['count']) { + $relationship = CRM_Utils_Array::first($relationships['values']); + + // returns the contact id on the other side of the relationship: + return (int) $relationship['contact_id_a'] === (int) $targetContactId + ? $relationship['contact_id_b'] + : $relationship['contact_id_a']; + } + else { + return NULL; + } + } + + /** + * Determines if the given relationship type is bidirectional or not by + * comparing their labels. + * + * @return bool + */ + protected function isBidirectionalRelationshipType($relationshipTypeId) { + $relationshipTypeResult = civicrm_api3('RelationshipType', 'get', [ + 'id' => $relationshipTypeId, + 'options' => ['limit' => 1], + ]); + + if ($relationshipTypeResult['count'] === 0) { + return FALSE; + } + + $relationshipType = CRM_Utils_Array::first($relationshipTypeResult['values']); + + return $relationshipType['label_b_a'] === $relationshipType['label_a_b']; + } + + /** + * Returns the activity's default assignee for a specific contact if the contact exists, + * otherwise returns null. + * + * @param object $activityTypeXML + * + * @return int|null + */ + protected function getDefaultAssigneeBySpecificContact($activityTypeXML) { + if (!$activityTypeXML->default_assignee_contact) { + return NULL; + } + + $contact = civicrm_api3('Contact', 'get', [ + 'id' => $activityTypeXML->default_assignee_contact, + ]); + + if ($contact['count'] == 1) { + return $activityTypeXML->default_assignee_contact; + } + + return NULL; + } + /** * @param $activitySetsXML * * @return array */ public static function activitySets($activitySetsXML) { - $result = array(); + $result = []; foreach ($activitySetsXML as $activitySetXML) { foreach ($activitySetXML as $recordXML) { $activitySetName = (string ) $recordXML->name; @@ -616,11 +780,12 @@ public function getCaseManagerRoleId($caseType) { /** * @param string $caseType + * * @return array<\Civi\CCase\CaseChangeListener> */ public function getListeners($caseType) { $xml = $this->retrieve($caseType); - $listeners = array(); + $listeners = []; if ($xml->Listeners && $xml->Listeners->Listener) { foreach ($xml->Listeners->Listener as $listenerXML) { $class = (string) $listenerXML; @@ -634,8 +799,7 @@ public function getListeners($caseType) { * @return int */ public function getRedactActivityEmail() { - $xml = $this->retrieve("Settings"); - return ( string ) $xml->RedactActivityEmail ? 1 : 0; + return $this->getBoolSetting('civicaseRedactActivityEmail', 'RedactActivityEmail'); } /** @@ -645,11 +809,7 @@ public function getRedactActivityEmail() { * 1 if allowed, 0 if not */ public function getAllowMultipleCaseClients() { - $xml = $this->retrieve("Settings"); - if ($xml) { - return ( string ) $xml->AllowMultipleCaseClients ? 1 : 0; - } - return 0; + return $this->getBoolSetting('civicaseAllowMultipleClients', 'AllowMultipleCaseClients'); } /** @@ -659,8 +819,90 @@ public function getAllowMultipleCaseClients() { * 1 if natural, 0 if alphabetic */ public function getNaturalActivityTypeSort() { - $xml = $this->retrieve("Settings"); - return ( string ) $xml->NaturalActivityTypeSort ? 1 : 0; + return $this->getBoolSetting('civicaseNaturalActivityTypeSort', 'NaturalActivityTypeSort'); + } + + /** + * @param string $settingKey + * @param string $xmlTag + * @param mixed $default + * + * @return int + */ + private function getBoolSetting($settingKey, $xmlTag, $default = 0) { + $setting = Civi::settings()->get($settingKey); + if ($setting !== 'default') { + return (int) $setting; + } + if ($xml = $this->retrieve("Settings")) { + return (string) $xml->{$xmlTag} ? 1 : 0; + } + return $default; + } + + /** + * At some point name and label got mixed up for case roles. + * Check against known machine name values, and then if no match check + * against labels. + * This is subject to some edge cases, but we catch those with a system + * status check. + * We do this to avoid requiring people to update their xml files which can + * be stored in external files we can't/don't want to edit. + * + * @param SimpleXMLElement $xml + * + * @return array[bool|string,string] + */ + public function locateNameOrLabel($xml) { + $lookupString = (string) $xml->name; + + // Don't use pseudoconstant because we need everything both name and + // label and disabled types. + $relationshipTypes = civicrm_api3('RelationshipType', 'get', [ + 'options' => ['limit' => 0], + ])['values']; + + // First look and see if it matches a machine name in the system. + // There are some edge cases here where we've actually been passed in a + // display label and it happens to match the machine name for a different + // db entry, but we have a system status check. + // But, we do want to check against the a_b version first, because of the + // way direction matters and that for bidirectional only one is present in + // the list where this eventually gets used, so return that first. + $relationshipTypeMachineNames = array_column($relationshipTypes, 'id', 'name_a_b'); + if (isset($relationshipTypeMachineNames[$lookupString])) { + return ["{$relationshipTypeMachineNames[$lookupString]}_b_a", $lookupString]; + } + $relationshipTypeMachineNames = array_column($relationshipTypes, 'id', 'name_b_a'); + if (isset($relationshipTypeMachineNames[$lookupString])) { + return ["{$relationshipTypeMachineNames[$lookupString]}_a_b", $lookupString]; + } + + // Now at this point assume we've been passed a display label, so find + // what it matches and return the associated machine name. This is a bit + // trickier because suppose somebody has changed the display labels so + // that they are now the same, but the machine names are different. We + // don't know which to return and so while it's the right relationship type + // it might be the backwards direction. We have to pick one to try first. + + $relationshipTypeDisplayLabels = array_column($relationshipTypes, 'id', 'label_a_b'); + if (isset($relationshipTypeDisplayLabels[$lookupString])) { + return [ + "{$relationshipTypeDisplayLabels[$lookupString]}_b_a", + $relationshipTypes[$relationshipTypeDisplayLabels[$lookupString]]['name_a_b'], + ]; + } + $relationshipTypeDisplayLabels = array_column($relationshipTypes, 'id', 'label_b_a'); + if (isset($relationshipTypeDisplayLabels[$lookupString])) { + return [ + "{$relationshipTypeDisplayLabels[$lookupString]}_a_b", + $relationshipTypes[$relationshipTypeDisplayLabels[$lookupString]]['name_b_a'], + ]; + } + + // Just go with what we were passed in, even though it doesn't seem + // to match *anything*. This was what it did before. + return [FALSE, $lookupString]; } } diff --git a/CRM/Case/XMLProcessor/Report.php b/CRM/Case/XMLProcessor/Report.php index 0a900c54b81d..2915874f79e5 100644 --- a/CRM/Case/XMLProcessor/Report.php +++ b/CRM/Case/XMLProcessor/Report.php @@ -1,9 +1,9 @@ $rule) { + 'redactionStringRules', + 'redactionRegexRules', + ) as $key => $rule) { $$rule = CRM_Case_PseudoConstant::redactionRule($key); if (!empty($$rule)) { @@ -129,20 +129,9 @@ public function &caseInfo( $case['subject'] = $dao->subject; $case['start_date'] = $dao->start_date; $case['end_date'] = $dao->end_date; - // FIXME: when we resolve if case_type_is single or multi-select - if (strpos($dao->case_type_id, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) { - $caseTypeID = substr($dao->case_type_id, 1, -1); - } - else { - $caseTypeID = $dao->case_type_id; - } - $caseTypeIDs = explode(CRM_Core_DAO::VALUE_SEPARATOR, - $dao->case_type_id - ); - $case['caseType'] = CRM_Case_BAO_Case::getCaseType($caseID); $case['caseTypeName'] = CRM_Case_BAO_Case::getCaseType($caseID, 'name'); - $case['status'] = CRM_Core_OptionGroup::getLabel('case_status', $dao->status_id, FALSE); + $case['status'] = CRM_Core_PseudoConstant::getLabel('CRM_Case_BAO_Case', 'status_id', $dao->status_id); } return $case; } @@ -267,7 +256,7 @@ public function &getActivityInfo($clientID, $activityID, $anyActivity = FALSE, $ $joinCaseActivity = " INNER JOIN civicrm_case_activity ca ON a.id = ca.activity_id "; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); @@ -285,7 +274,7 @@ public function &getActivityInfo($clientID, $activityID, $anyActivity = FALSE, $ if ($dao->fetch()) { //if activity type is email get info of all activities. - if ($dao->activity_type_id == CRM_Core_OptionGroup::getValue('activity_type', 'Email', 'name')) { + if ($dao->activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'activity_type_id', 'Email')) { $anyActivity = TRUE; } $activityTypes = CRM_Case_PseudoConstant::caseActivityType(FALSE, $anyActivity); @@ -353,7 +342,7 @@ public function &getActivity($clientID, $activityDAO, &$activityTypeInfo) { ); } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); if (!empty($activityDAO->targetID)) { @@ -425,7 +414,7 @@ public function &getActivity($clientID, $activityDAO, &$activityTypeInfo) { 'value' => $this->redact($creator), 'type' => 'String', ); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); $source_contact_id = CRM_Activity_BAO_Activity::getActivityContact($activityDAO->id, $sourceID); $reporter = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', @@ -477,9 +466,7 @@ public function &getActivity($clientID, $activityDAO, &$activityTypeInfo) { if ($activityDAO->medium_id) { $activity['fields'][] = array( 'label' => ts('Medium'), - 'value' => CRM_Core_OptionGroup::getLabel('encounter_medium', - $activityDAO->medium_id, FALSE - ), + 'value' => CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'medium_id', $activityDAO->medium_id), 'type' => 'String', ); } @@ -512,7 +499,7 @@ public function &getActivity($clientID, $activityDAO, &$activityTypeInfo) { } $activity['fields'][] = array( 'label' => ts('Status'), - 'value' => CRM_Core_OptionGroup::getLabel('activity_status', + 'value' => CRM_Core_PseudoConstant::getLabel('CRM_Activity_DAO_Activity', 'activity_status_id', $activityDAO->status_id ), 'type' => 'String', @@ -520,7 +507,7 @@ public function &getActivity($clientID, $activityDAO, &$activityTypeInfo) { $activity['fields'][] = array( 'label' => ts('Priority'), - 'value' => CRM_Core_OptionGroup::getLabel('priority', + 'value' => CRM_Core_PseudoConstant::getLabel('CRM_Activity_DAO_Activity', 'priority_id', $activityDAO->priority_id ), 'type' => 'String', @@ -558,7 +545,7 @@ public function getCustomData($clientID, $activityDAO, &$activityTypeInfo) { $value = $dao->$columnName; } else { - $value = CRM_Core_BAO_CustomField::displayValue($dao->$columnName, $typeValue['fieldID']); + $value = CRM_Core_BAO_CustomField::displayValue($dao->$columnName, $typeValue['fieldID'], $activityDAO->id); } if ($value) { @@ -572,10 +559,6 @@ public function getCustomData($clientID, $activityDAO, &$activityTypeInfo) { ) { $value = $this->redact($value); } - elseif (CRM_Utils_Array::value('type', $typeValue) == 'File') { - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile', $typeValue, 'entity_table'); - $value = CRM_Core_BAO_File::attachmentInfo($tableName, $activityDAO->id); - } elseif (CRM_Utils_Array::value('type', $typeValue) == 'Link') { $value = CRM_Utils_System::formatWikiURL($value); } @@ -598,10 +581,11 @@ public function getCustomData($clientID, $activityDAO, &$activityTypeInfo) { /** * @param int $activityTypeID * @param null $dateFormat + * @param bool $onlyActive * * @return mixed */ - public function getActivityTypeCustomSQL($activityTypeID, $dateFormat = NULL) { + public function getActivityTypeCustomSQL($activityTypeID, $dateFormat = NULL, $onlyActive = TRUE) { static $cache = array(); if (is_null($activityTypeID)) { @@ -625,6 +609,9 @@ public function getActivityTypeCustomSQL($activityTypeID, $dateFormat = NULL) { AND cg.extends = 'Activity' AND " . CRM_Core_Permission::customGroupClause(CRM_Core_Permission::VIEW, 'cg.'); + if ($onlyActive) { + $query .= " AND cf.is_active = 1 "; + } if ($activityTypeID) { $query .= "AND ( cg.extends_entity_column_value IS NULL OR cg.extends_entity_column_value LIKE '%" . CRM_Core_DAO::VALUE_SEPARATOR . "%1" . CRM_Core_DAO::VALUE_SEPARATOR . "%' )"; } @@ -837,8 +824,8 @@ public static function printCaseReport() { $xmlProcessor = new CRM_Case_XMLProcessor_Process(); $caseRoles = $xmlProcessor->get($caseType, 'CaseRoles'); foreach ($caseRelationships as $key => & $value) { - if (!empty($caseRoles[$value['relation_type']])) { - unset($caseRoles[$value['relation_type']]); + if (!empty($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']])) { + unset($caseRoles[$value['relation_type'] . '_' . $value['relationship_direction']]); } if ($isRedact) { if (!array_key_exists($value['name'], $report->_redactionStringRules)) { @@ -979,8 +966,8 @@ public static function printCaseReport() { $extends = array('case'); $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); $caseCustomFields = array(); - while (list($gid, $group_values) = each($groupTree)) { - while (list($id, $field_values) = each($group_values['fields'])) { + foreach ($groupTree as $gid => $group_values) { + foreach ($group_values['fields'] as $id => $field_values) { if (array_key_exists($id, $customValues)) { $caseCustomFields[$gid]['title'] = $group_values['title']; $caseCustomFields[$gid]['values'][$id] = array( diff --git a/CRM/Case/XMLProcessor/Settings.php b/CRM/Case/XMLProcessor/Settings.php index 8ec1162894ae..dbe1dedb432d 100644 --- a/CRM/Case/XMLProcessor/Settings.php +++ b/CRM/Case/XMLProcessor/Settings.php @@ -1,9 +1,9 @@ */ - protected $xml = array(); + protected $xml = []; /** - * @var array|NULL + * @var array|null */ protected $hookCache = NULL; /** - * @var array|NULL symbolic names of case-types + * Symbolic names of case-types. + * + * @var array|null */ protected $allCaseTypes = NULL; @@ -63,13 +65,20 @@ public static function singleton($fresh = FALSE) { return self::$singleton; } + public function flush() { + $this->xml = []; + $this->hookCache = NULL; + $this->allCaseTypes = NULL; + CRM_Core_DAO::$_dbColumnValueCache = []; + } + /** * Class constructor. * * @param array $allCaseTypes * @param array $xml */ - public function __construct($allCaseTypes = NULL, $xml = array()) { + public function __construct($allCaseTypes = NULL, $xml = []) { $this->allCaseTypes = $allCaseTypes; $this->xml = $xml; } @@ -169,14 +178,14 @@ public function findXmlFile($caseType) { if (isset($config->customTemplateDir) && $config->customTemplateDir) { // check if the file exists in the custom templates directory $fileName = implode(DIRECTORY_SEPARATOR, - array( + [ $config->customTemplateDir, 'CRM', 'Case', 'xml', 'configuration', "$caseType.xml", - ) + ] ); } } @@ -185,24 +194,24 @@ public function findXmlFile($caseType) { if (!file_exists($fileName)) { // check if file exists locally $fileName = implode(DIRECTORY_SEPARATOR, - array( + [ dirname(__FILE__), 'xml', 'configuration', "$caseType.xml", - ) + ] ); } if (!file_exists($fileName)) { // check if file exists locally $fileName = implode(DIRECTORY_SEPARATOR, - array( + [ dirname(__FILE__), 'xml', 'configuration.sample', "$caseType.xml", - ) + ] ); } } @@ -215,7 +224,7 @@ public function findXmlFile($caseType) { */ public function getCaseTypesViaHook() { if ($this->hookCache === NULL) { - $this->hookCache = array(); + $this->hookCache = []; CRM_Utils_Hook::caseTypes($this->hookCache); } return $this->hookCache; @@ -235,7 +244,7 @@ public function getAllCaseTypes() { * @return array symbolic-names of activity-types */ public function getAllDeclaredActivityTypes() { - $result = array(); + $result = []; $p = new CRM_Case_XMLProcessor_Process(); foreach ($this->getAllCaseTypes() as $caseTypeName) { @@ -249,10 +258,12 @@ public function getAllDeclaredActivityTypes() { } /** + * Relationships are straight from XML, described from perspective of non-client + * * @return array symbolic-names of relationship-types */ public function getAllDeclaredRelationshipTypes() { - $result = array(); + $result = []; $p = new CRM_Case_XMLProcessor_Process(); foreach ($this->getAllCaseTypes() as $caseTypeName) { diff --git a/CRM/Case/xml/Menu/Case.xml b/CRM/Case/xml/Menu/Case.xml index 2d270503cc80..91d5ceb984d2 100644 --- a/CRM/Case/xml/Menu/Case.xml +++ b/CRM/Case/xml/Menu/Case.xml @@ -64,6 +64,14 @@ Case Details CRM_Case_Page_CaseDetails
    + + civicrm/admin/setting/case + CiviCase Settings + CRM_Admin_Form_Setting_Case + CiviCase + admin/small/36.png + 380 + civicrm/admin/options/case_type Case Types @@ -128,4 +136,8 @@ civicrm/ajax/delcaserole CRM_Case_Page_AJAX::deleteCaseRoles + + civicrm/ajax/get-cases + CRM_Case_Page_AJAX::getCases + diff --git a/CRM/Case/xml/configuration.sample/Settings.xml b/CRM/Case/xml/configuration.sample/Settings.xml index 58dbb7cff983..424eb7afdffe 100644 --- a/CRM/Case/xml/configuration.sample/Settings.xml +++ b/CRM/Case/xml/configuration.sample/Settings.xml @@ -1,16 +1,30 @@ + + + - 0 + + 0 + + 0 + + 0 + diff --git a/CRM/Contact/ActionMapping.php b/CRM/Contact/ActionMapping.php index 8bb7ea691480..2f5f7b28d7de 100644 --- a/CRM/Contact/ActionMapping.php +++ b/CRM/Contact/ActionMapping.php @@ -1,9 +1,9 @@ register(CRM_Contact_ActionMapping::create(array( + $registrations->register(CRM_Contact_ActionMapping::create([ 'id' => CRM_Contact_ActionMapping::CONTACT_MAPPING_ID, 'entity' => 'civicrm_contact', 'entity_label' => ts('Contact'), @@ -60,14 +59,14 @@ public static function onRegisterActionMappings(\Civi\ActionSchedule\Event\Mappi 'entity_status' => 'contact_date_reminder_options', 'entity_status_label' => ts('Annual Options'), 'entity_date_start' => 'date_field', - ))); + ])); } - private $contactDateFields = array( + private $contactDateFields = [ 'birth_date', 'created_date', 'modified_date', - ); + ]; /** * Determine whether a schedule based on this mapping is sufficiently @@ -79,7 +78,7 @@ public static function onRegisterActionMappings(\Civi\ActionSchedule\Event\Mappi * List of error messages. */ public function validateSchedule($schedule) { - $errors = array(); + $errors = []; if (CRM_Utils_System::isNull($schedule->entity_value) || $schedule->entity_value === '0') { $errors['entity'] = ts('Please select a specific date field.'); } @@ -119,30 +118,31 @@ public function createQuery($schedule, $phase, $defaultParams) { elseif (in_array($selectedValues[0], $this->contactDateFields)) { $dateDBField = $selectedValues[0]; $query = \CRM_Utils_SQL_Select::from("{$this->entity} e")->param($defaultParams); - $query->param(array( + $query->param([ 'casAddlCheckFrom' => 'civicrm_contact e', 'casContactIdField' => 'e.id', 'casEntityIdField' => 'e.id', 'casContactTableAlias' => 'e', - )); + ]); $query->where('e.is_deleted = 0 AND e.is_deceased = 0'); } else { //custom field - $customFieldParams = array('id' => substr($selectedValues[0], 7)); - $customGroup = $customField = array(); + $customFieldParams = ['id' => substr($selectedValues[0], 7)]; + $customGroup = $customField = []; \CRM_Core_BAO_CustomField::retrieve($customFieldParams, $customField); $dateDBField = $customField['column_name']; - $customGroupParams = array('id' => $customField['custom_group_id'], $customGroup); + $customGroupParams = ['id' => $customField['custom_group_id'], $customGroup]; \CRM_Core_BAO_CustomGroup::retrieve($customGroupParams, $customGroup); $query = \CRM_Utils_SQL_Select::from("{$customGroup['table_name']} e")->param($defaultParams); - $query->param(array( + $query->param([ 'casAddlCheckFrom' => "{$customGroup['table_name']} e", 'casContactIdField' => 'e.entity_id', 'casEntityIdField' => 'e.id', 'casContactTableAlias' => NULL, - )); - $query->where('1'); // possible to have no "where" in this case + ]); + // possible to have no "where" in this case + $query->where('1'); } $query['casDateField'] = 'e.' . $dateDBField; diff --git a/CRM/Contact/BAO/Contact.php b/CRM/Contact/BAO/Contact.php index d547e2a6e585..2e5927e9cbe3 100644 --- a/CRM/Contact/BAO/Contact.php +++ b/CRM/Contact/BAO/Contact.php @@ -1,9 +1,9 @@ preferred_communication_method = CRM_Utils_Array::implodePadded($params['preferred_communication_method']); unset($params['preferred_communication_method']); - - CRM_Utils_Array::formatArrayKeys($prefComm); - $prefComm = CRM_Utils_Array::implodePadded($prefComm); } - $contact->preferred_communication_method = $prefComm; + $defaults = ['source' => CRM_Utils_Array::value('contact_source', $params)]; + if ($params['contact_type'] === 'Organization' && isset($params['organization_name'])) { + $defaults['display_name'] = $params['organization_name']; + $defaults['sort_name'] = $params['organization_name']; + } + if ($params['contact_type'] === 'Household' && isset($params['household_name'])) { + $defaults['display_name'] = $params['household_name']; + $defaults['sort_name'] = $params['household_name']; + } + $params = array_merge($defaults, $params); $allNull = $contact->copyValues($params); $contact->id = CRM_Utils_Array::value('contact_id', $params); - if ($contact->contact_type == 'Individual') { + if ($contact->contact_type === 'Individual') { $allNull = FALSE; - + // @todo allow the lines below to be overridden by input or hooks & add tests, + // as has been done for households and organizations. // Format individual fields. CRM_Contact_BAO_Individual::format($params, $contact); } - elseif ($contact->contact_type == 'Household') { - if (isset($params['household_name'])) { - $allNull = FALSE; - $contact->display_name = $contact->sort_name = CRM_Utils_Array::value('household_name', $params, ''); - } + + if (strlen($contact->display_name) > 128) { + $contact->display_name = substr($contact->display_name, 0, 128); } - elseif ($contact->contact_type == 'Organization') { - if (isset($params['organization_name'])) { - $allNull = FALSE; - $contact->display_name = $contact->sort_name = CRM_Utils_Array::value('organization_name', $params, ''); - } + if (strlen($contact->sort_name) > 128) { + $contact->sort_name = substr($contact->sort_name, 0, 128); } $privacy = CRM_Utils_Array::value('privacy', $params); @@ -218,7 +213,7 @@ public static function add(&$params) { ); } - if ($contact->contact_type == 'Individual' && (isset($params['current_employer']) || isset($params['employer_id']))) { + if ($contact->contact_type === 'Individual' && (isset($params['current_employer']) || isset($params['employer_id']))) { // Create current employer. $newEmployer = !empty($params['employer_id']) ? $params['employer_id'] : CRM_Utils_Array::value('current_employer', $params); @@ -237,7 +232,7 @@ public static function add(&$params) { } // Update cached employer name. - if ($contact->contact_type == 'Organization') { + if ($contact->contact_type === 'Organization') { CRM_Contact_BAO_Contact_Utils::updateCurrentEmployer($contact->id); } @@ -262,12 +257,12 @@ public static function add(&$params) { * @param bool $skipDelete * Unclear parameter, passed to website create * - * @todo explain this parameter - * - * @throws Exception * @return CRM_Contact_BAO_Contact|CRM_Core_Error * Created or updated contribution object. We are deprecating returning an error in * favour of exceptions + * @throws Exception + * @todo explain this parameter + * */ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE, $skipDelete = FALSE) { $contact = NULL; @@ -275,18 +270,29 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE return $contact; } - $isEdit = TRUE; + $isEdit = !empty($params['contact_id']); + + if ($isEdit && empty($params['contact_type'])) { + $params['contact_type'] = self::getContactType($params['contact_id']); + } + + if (!empty($params['check_permissions']) && isset($params['api_key']) + && !CRM_Core_Permission::check([['edit api keys', 'administer CiviCRM']]) + && !($isEdit && CRM_Core_Permission::check('edit own api keys') && $params['contact_id'] == CRM_Core_Session::getLoggedInContactID()) + ) { + throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify api key'); + } + if ($invokeHooks) { if (!empty($params['contact_id'])) { CRM_Utils_Hook::pre('edit', $params['contact_type'], $params['contact_id'], $params); } else { CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params); - $isEdit = FALSE; } } - $config = CRM_Core_Config::singleton(); + self::ensureGreetingParamsAreSet($params); // CRM-6942: set preferred language to the current language if it’s unset (and we’re creating a contact). if (empty($params['contact_id'])) { @@ -295,15 +301,10 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE $params['preferred_language'] = $language; } - // CRM-9739: set greeting & addressee if unset and we’re creating a contact. - foreach (self::$_greetingTypes as $greeting) { - if (empty($params[$greeting . '_id'])) { - if ($defaultGreetingTypeId - = CRM_Contact_BAO_Contact_Utils::defaultGreeting($params['contact_type'], $greeting) - ) { - $params[$greeting . '_id'] = $defaultGreetingTypeId; - } - } + // CRM-21041: set default 'Communication Style' if unset when creating a contact. + if (empty($params['communication_style_id'])) { + $defaultCommunicationStyleId = CRM_Core_OptionGroup::values('communication_style', TRUE, NULL, NULL, 'AND is_default = 1'); + $params['communication_style_id'] = array_pop($defaultCommunicationStyleId); } } @@ -327,13 +328,13 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE $params['group'][$domainGroupID] = 1; } else { - $params['group'] = array($domainGroupID => 1); + $params['group'] = [$domainGroupID => 1]; } } } if (array_key_exists('group', $params)) { - $contactIds = array($params['contact_id']); + $contactIds = [$params['contact_id']]; foreach ($params['group'] as $groupId => $flag) { if ($flag == 1) { CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $groupId); @@ -353,12 +354,11 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE $skipDelete = TRUE; } - //add website - CRM_Core_BAO_Website::create($params['website'], $contact->id, $skipDelete); + if (isset($params['website'])) { + CRM_Core_BAO_Website::process($params['website'], $contact->id, $skipDelete); + } - //get userID from session - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); + $userID = CRM_Core_Session::singleton()->get('userID'); // add notes if (!empty($params['note'])) { if (is_array($params['note'])) { @@ -372,34 +372,31 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE $contactId = $userID; } - $noteParams = array( + $noteParams = [ 'entity_id' => $contact->id, 'entity_table' => 'civicrm_contact', 'note' => $note['note'], 'subject' => CRM_Utils_Array::value('subject', $note), 'contact_id' => $contactId, - ); - CRM_Core_BAO_Note::add($noteParams, CRM_Core_DAO::$_nullArray); + ]; + CRM_Core_BAO_Note::add($noteParams); } } else { $contactId = $contact->id; - if (isset($note['contact_id'])) { - $contactId = $note['contact_id']; - } //if logged in user, overwrite contactId if ($userID) { $contactId = $userID; } - $noteParams = array( + $noteParams = [ 'entity_id' => $contact->id, 'entity_table' => 'civicrm_contact', 'note' => $params['note'], 'subject' => CRM_Utils_Array::value('subject', $params), 'contact_id' => $contactId, - ); - CRM_Core_BAO_Note::add($noteParams, CRM_Core_DAO::$_nullArray); + ]; + CRM_Core_BAO_Note::add($noteParams); } } @@ -414,11 +411,11 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE // make a civicrm_subscription_history entry only on contact create (CRM-777) if (empty($params['contact_id'])) { - $subscriptionParams = array( + $subscriptionParams = [ 'contact_id' => $contact->id, 'status' => 'Added', 'method' => 'Admin', - ); + ]; CRM_Contact_BAO_SubscriptionHistory::create($subscriptionParams); } @@ -432,13 +429,7 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE 'name' ); - if (!$config->doNotResetCache) { - // Note: doNotResetCache flag is currently set by import contact process and merging, - // since resetting and - // rebuilding cache could be expensive (for many contacts). We might come out with better - // approach in future. - CRM_Contact_BAO_Contact_Utils::clearContactCaches($contact->id); - } + CRM_Contact_BAO_Contact_Utils::clearContactCaches(); if ($invokeHooks) { if ($isEdit) { @@ -449,12 +440,93 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE } } - // process greetings CRM-4575, cache greetings - self::processGreetings($contact); + // In order to prevent a series of expensive queries in intensive batch processing + // api calls may pass in skip_greeting_processing, probably doing it later via the + // scheduled job. CRM-21551 + if (empty($params['skip_greeting_processing'])) { + self::processGreetings($contact); + } + + if (!empty($params['check_permissions'])) { + $contacts = [&$contact]; + self::unsetProtectedFields($contacts); + } return $contact; } + /** + * Format the output of the create contact function + * + * @param CRM_Contact_DAO_Contact[]|array[] $contacts + */ + public static function unsetProtectedFields(&$contacts) { + if (!CRM_Core_Permission::check([['edit api keys', 'administer CiviCRM']])) { + $currentUser = CRM_Core_Session::getLoggedInContactID(); + $editOwn = $currentUser && CRM_Core_Permission::check('edit own api keys'); + foreach ($contacts as &$contact) { + $cid = is_object($contact) ? $contact->id : CRM_Utils_Array::value('id', $contact); + if (!($editOwn && $cid == $currentUser)) { + if (is_object($contact)) { + unset($contact->api_key); + } + else { + unset($contact['api_key']); + } + } + } + } + } + + /** + * Ensure greeting parameters are set. + * + * By always populating greetings here we can be sure they are set if required & avoid a call later. + * (ie. knowing we have definitely tried disambiguates between NULL & not loaded.) + * + * @param array $params + * + * @throws \CiviCRM_API3_Exception + */ + public static function ensureGreetingParamsAreSet(&$params) { + $allGreetingParams = ['addressee' => 'addressee_id', 'postal_greeting' => 'postal_greeting_id', 'email_greeting' => 'email_greeting_id']; + $missingGreetingParams = []; + + foreach ($allGreetingParams as $greetingIndex => $greetingParam) { + if (empty($params[$greetingParam])) { + $missingGreetingParams[$greetingIndex] = $greetingParam; + } + } + + if (!empty($params['contact_id']) && !empty($missingGreetingParams)) { + $savedGreetings = civicrm_api3('Contact', 'getsingle', [ + 'id' => $params['contact_id'], + 'return' => array_keys($missingGreetingParams), + ]); + + foreach (array_keys($missingGreetingParams) as $missingGreetingParam) { + if (!empty($savedGreetings[$missingGreetingParam . '_custom'])) { + $missingGreetingParams[$missingGreetingParam . '_custom'] = $missingGreetingParam . '_custom'; + } + } + // Filter out other fields. + $savedGreetings = array_intersect_key($savedGreetings, array_flip($missingGreetingParams)); + $params = array_merge($params, $savedGreetings); + } + else { + foreach ($missingGreetingParams as $greetingName => $greeting) { + $params[$greeting] = CRM_Contact_BAO_Contact_Utils::defaultGreeting($params['contact_type'], $greetingName); + } + } + + foreach ($allGreetingParams as $greetingIndex => $greetingParam) { + if ($params[$greetingParam] === 'null') { + // If we are setting it to null then null out the display field. + $params[$greetingIndex . '_display'] = 'null'; + } + } + } + /** * Get the display name and image of a contact. * @@ -480,10 +552,10 @@ public static function getDisplayAndImage($id, $includeTypeInReturnParameters = $dao->query($sql); if ($dao->fetch()) { $image = CRM_Contact_BAO_Contact_Utils::getImage($dao->contact_sub_type ? - $dao->contact_sub_type : $dao->contact_type, FALSE, $id + $dao->contact_sub_type : $dao->contact_type, FALSE, $id ); $imageUrl = CRM_Contact_BAO_Contact_Utils::getImage($dao->contact_sub_type ? - $dao->contact_sub_type : $dao->contact_type, TRUE, $id + $dao->contact_sub_type : $dao->contact_type, TRUE, $id ); // use email if display_name is empty @@ -496,13 +568,13 @@ public static function getDisplayAndImage($id, $includeTypeInReturnParameters = CRM_Utils_Hook::alterDisplayName($displayName, $id, $dao); - return $includeTypeInReturnParameters ? array( + return $includeTypeInReturnParameters ? [ $displayName, $image, $dao->contact_type, $dao->contact_sub_type, $imageUrl, - ) : array($displayName, $image, $imageUrl); + ] : [$displayName, $image, $imageUrl]; } return NULL; } @@ -511,16 +583,18 @@ public static function getDisplayAndImage($id, $includeTypeInReturnParameters = * Add billing fields to the params if appropriate. * * If we have ANY name fields then we want to ignore all the billing name fields. However, if we - * don't then we should set the name fields to the filling fields AND add the preserveDBName + * don't then we should set the name fields to the billing fields AND add the preserveDBName * parameter (which will tell the BAO only to set those fields if none already exist. * * We specifically don't want to set first name from billing and last name form an on-page field. Mixing & * matching is best done by hipsters. * * @param array $params + * + * @fixme How does this relate to almost the same thing being done in CRM_Core_Form::formatParamsForPaymentProcessor() */ public static function addBillingNameFieldsIfOtherwiseNotSet(&$params) { - $nameFields = array('first_name', 'middle_name', 'last_name', 'nick_name', 'prefix_id', 'suffix_id'); + $nameFields = ['first_name', 'middle_name', 'last_name', 'nick_name', 'prefix_id', 'suffix_id']; foreach ($nameFields as $field) { if (!empty($params[$field])) { return; @@ -536,6 +610,105 @@ public static function addBillingNameFieldsIfOtherwiseNotSet(&$params) { } + /** + * Resolve a state province string (UT or Utah) to an ID. + * + * If country has been passed in we should select a state belonging to that country. + * + * Alternatively we should choose from enabled countries, prioritising the default country. + * + * @param array $values + * @param int|null $countryID + * + * @return int|null + * + * @throws \CRM_Core_Exception + */ + protected static function resolveStateProvinceID($values, $countryID) { + + if ($countryID) { + $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($countryID); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($countryID, 'abbreviation'); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + return NULL; + } + else { + // The underlying lookupValue function needs some de-fanging. Until that has been unravelled we + // continue to resolve stateprovince lists in descending order of preference & just 'keep trying'. + // prefer matching country.. + $stateProvinceList = CRM_Core_BAO_Address::buildOptions('state_province_id', NULL, ['country_id' => Civi::settings()->get('defaultContactCountry')]); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + + $stateProvinceList = CRM_Core_PseudoConstant::stateProvince(); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + + $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceAbbreviationForDefaultCountry(); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceAbbreviation(); + if (CRM_Utils_Array::lookupValue($values, + 'state_province', + $stateProvinceList, + TRUE + )) { + return $values['state_province_id']; + } + } + + return NULL; + } + + /** + * Get the relevant location entity for the array key. + * + * Based on the field name we determine which location entity + * we are dealing with. Apart from a few specific ones they + * are mostly 'address' (the default). + * + * @param string $fieldName + * + * @return string + */ + protected static function getLocationEntityForKey($fieldName) { + if (in_array($fieldName, ['email', 'phone', 'im', 'openid'])) { + return $fieldName; + } + if ($fieldName === 'phone_ext') { + return 'phone'; + } + return 'address'; + } + /** * Create last viewed link to recently updated contact. * @@ -552,26 +725,26 @@ public static function addBillingNameFieldsIfOtherwiseNotSet(&$params) { public function createDefaultCrudLink($crudLinkSpec) { switch ($crudLinkSpec['action']) { case CRM_Core_Action::VIEW: - $result = array( + $result = [ 'title' => $this->display_name, 'path' => 'civicrm/contact/view', - 'query' => array( + 'query' => [ 'reset' => 1, 'cid' => $this->id, - ), - ); + ], + ]; return $result; case CRM_Core_Action::UPDATE: - $result = array( + $result = [ 'title' => $this->display_name, 'path' => 'civicrm/contact/add', - 'query' => array( + 'query' => [ 'reset' => 1, 'action' => 'update', 'cid' => $this->id, - ), - ); + ], + ]; return $result; } return NULL; @@ -583,7 +756,13 @@ public function createDefaultCrudLink($crudLinkSpec) { * @param array $defaults * (reference) the default values, some of which need to be resolved. * @param bool $reverse - * True if we want to resolve the values in the reverse direction (value -> name). + * Always true as this function is only called from one place.. + * + * @deprecated + * + * This is called specifically from the contact import parser & should be moved there + * as it is not truly a generic function. + * */ public static function resolveDefaults(&$defaults, $reverse = FALSE) { // Hack for birth_date. @@ -600,16 +779,16 @@ public static function resolveDefaults(&$defaults, $reverse = FALSE) { //lookup value of email/postal greeting, addressee, CRM-4575 foreach (self::$_greetingTypes as $greeting) { - $filterCondition = array( + $filterCondition = [ 'contact_type' => CRM_Utils_Array::value('contact_type', $defaults), 'greeting_type' => $greeting, - ); + ]; CRM_Utils_Array::lookupValue($defaults, $greeting, CRM_Core_PseudoConstant::greeting($filterCondition), $reverse ); } - $blocks = array('address', 'im', 'phone'); + $blocks = ['address', 'im', 'phone']; foreach ($blocks as $name) { if (!array_key_exists($name, $defaults) || !is_array($defaults[$name])) { continue; @@ -622,7 +801,7 @@ public static function resolveDefaults(&$defaults, $reverse = FALSE) { if ($name == 'address') { // FIXME: lookupValue doesn't work for vcard_name if (!empty($values['location_type_id'])) { - $vcardNames = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array('labelColumn' => 'vcard_name')); + $vcardNames = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'vcard_name']); $values['vcard_name'] = $vcardNames[$values['location_type_id']]; } @@ -639,36 +818,9 @@ public static function resolveDefaults(&$defaults, $reverse = FALSE) { $reverse ); } - - // CRM-7597 - // if we find a country id above, we need to restrict it to that country - // rather than the list of all countries - - if (!empty($values['country_id'])) { - $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($values['country_id']); - } - else { - $stateProvinceList = CRM_Core_PseudoConstant::stateProvince(); - } - if (!CRM_Utils_Array::lookupValue($values, - 'state_province', - $stateProvinceList, - $reverse - ) && - $reverse - ) { - - if (!empty($values['country_id'])) { - $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($values['country_id'], 'abbreviation'); - } - else { - $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceAbbreviation(); - } - CRM_Utils_Array::lookupValue($values, - 'state_province', - $stateProvinceList, - $reverse - ); + $stateProvinceID = self::resolveStateProvinceID($values, CRM_Utils_Array::value('country_id', $values)); + if ($stateProvinceID) { + $values['state_province_id'] = $stateProvinceID; } if (!empty($values['state_province_id'])) { @@ -732,7 +884,7 @@ public static function &retrieve(&$params, &$defaults, $microformat = FALSE) { unset($params['id']); //get the block information for this contact - $entityBlock = array('contact_id' => $params['contact_id']); + $entityBlock = ['contact_id' => $params['contact_id']]; $blocks = CRM_Core_BAO_Location::getValues($entityBlock, $microformat); $defaults = array_merge($defaults, $blocks); foreach ($blocks as $block => $value) { @@ -803,7 +955,7 @@ public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALS // make sure we have edit permission for this contact // before we delete if ($checkPermissions && (($skipUndelete && !CRM_Core_Permission::check('delete contacts')) || - ($restore && !CRM_Core_Permission::check('access deleted contacts'))) + ($restore && !CRM_Core_Permission::check('access deleted contacts'))) ) { return FALSE; } @@ -811,7 +963,7 @@ public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALS // CRM-12929 // Restrict contact to be delete if contact has financial trxns $error = NULL; - if ($skipUndelete && CRM_Financial_BAO_FinancialItem::checkContactPresent(array($id), $error)) { + if ($skipUndelete && CRM_Financial_BAO_FinancialItem::checkContactPresent([$id], $error)) { return FALSE; } @@ -898,7 +1050,10 @@ public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALS CRM_Utils_Recent::delContact($id); self::updateContactCache($id, empty($restore)); - // delete any dupe cache entry + // delete any prevnext/dupe cache entry + // These two calls are redundant in default deployments, but they're + // meaningful if "prevnext" is memory-backed. + Civi::service('prevnext')->deleteItem($id); CRM_Core_BAO_PrevNextCache::deleteItem($id); $transaction->commit(); @@ -907,10 +1062,6 @@ public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALS CRM_Utils_Hook::post('delete', $contactType, $contact->id, $contact); } - // also reset the DB_DO global array so we can reuse the memory - // http://issues.civicrm.org/jira/browse/CRM-4387 - CRM_Core_DAO::freeResult(); - return TRUE; } @@ -933,7 +1084,7 @@ public static function updateContactCache($contactID, $isTrashed = FALSE) { // a table-locking query. It still seems a bit inadequate as it assumes the acl users can't see deleted // but this should not cause any change as long as contacts are not being trashed outside the // main functions for that. - CRM_Core_DAO::executeQuery('DELETE FROM civicrm_acl_contact_cache WHERE contact_id = %1', array(1 => array($contactID, 'Integer'))); + CRM_Core_DAO::executeQuery('DELETE FROM civicrm_acl_contact_cache WHERE contact_id = %1', [1 => [$contactID, 'Integer']]); } else { CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); @@ -953,11 +1104,12 @@ public static function deleteContactImage($id) { if (!$id) { return FALSE; } - $query = " -UPDATE civicrm_contact -SET image_URL=NULL -WHERE id={$id}; "; - CRM_Core_DAO::executeQuery($query); + + $contact = new self(); + $contact->id = $id; + $contact->image_URL = 'null'; + $contact->save(); + return TRUE; } @@ -990,7 +1142,7 @@ public static function getThumbSize($imageWidth, $imageHeight) { $imageThumbWidth = round($thumbWidth * $imageRatio); } - return array($imageThumbWidth, $imageThumbHeight); + return [$imageThumbWidth, $imageThumbHeight]; } /** @@ -1013,7 +1165,7 @@ public static function processImageParams( $statusMsg = NULL, $opType = 'status' ) { - $mimeType = array( + $mimeType = [ 'image/jpeg', 'image/jpg', 'image/png', @@ -1021,7 +1173,7 @@ public static function processImageParams( 'image/p-jpeg', 'image/gif', 'image/x-png', - ); + ]; if (in_array($params[$imageIndex]['type'], $mimeType)) { $photo = basename($params[$imageIndex]['name']); @@ -1034,7 +1186,7 @@ public static function processImageParams( $statusMsg = ts('Image could not be uploaded due to invalid type extension.'); } if ($opType == 'status') { - CRM_Core_Session::setStatus($statusMsg, 'Sorry', 'error'); + CRM_Core_Session::setStatus($statusMsg, ts('Error'), 'error'); } // FIXME: additional support for fatal, bounce etc could be added. return FALSE; @@ -1074,14 +1226,14 @@ public static function processImage() { * @return bool */ public static function contactTrashRestore($contact, $restore = FALSE) { - $updateParams = array( + $updateParams = [ 'id' => $contact->id, 'is_deleted' => $restore ? 0 : 1, - ); + ]; CRM_Utils_Hook::pre('update', $contact->contact_type, $contact->id, $updateParams); - $params = array(1 => array($contact->id, 'Integer')); + $params = [1 => [$contact->id, 'Integer']]; if (!$restore) { $query = "DELETE FROM civicrm_uf_match WHERE contact_id = %1"; CRM_Core_DAO::executeQuery($query, $params); @@ -1089,6 +1241,7 @@ public static function contactTrashRestore($contact, $restore = FALSE) { $contact->copyValues($updateParams); $contact->save(); + CRM_Core_BAO_Log::register($contact->id, 'civicrm_contact', $contact->id); CRM_Utils_Hook::post('update', $contact->contact_type, $contact->id, $contact); @@ -1122,7 +1275,7 @@ public static function getContactType($id) { public static function getContactSubType($id, $implodeDelimiter = NULL) { $subtype = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'contact_sub_type'); if (!$subtype) { - return $implodeDelimiter ? NULL : array(); + return $implodeDelimiter ? NULL : []; } $subtype = CRM_Utils_Array::explodePadded($subtype); @@ -1142,16 +1295,16 @@ public static function getContactSubType($id, $implodeDelimiter = NULL) { * @return array */ public static function getContactTypes($id) { - $params = array('id' => $id); - $details = array(); + $params = ['id' => $id]; + $details = []; $contact = CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Contact', $params, $details, - array('contact_type', 'contact_sub_type') + ['contact_type', 'contact_sub_type'] ); if ($contact) { - $contactTypes = array(); + $contactTypes = []; if ($contact->contact_sub_type) { $contactTypes = CRM_Utils_Array::explodePadded($contact->contact_sub_type); } @@ -1202,24 +1355,20 @@ public static function importableFields( $cacheKeyString .= $showAll ? '_1' : '_0'; $cacheKeyString .= $isProfile ? '_1' : '_0'; $cacheKeyString .= $checkPermission ? '_1' : '_0'; + $cacheKeyString .= '_' . CRM_Core_Config::domainID() . '_'; - $fields = CRM_Utils_Array::value($cacheKeyString, self::$_importableFields); - - if (!$fields) { - // check if we can retrieve from database cache - $fields = CRM_Core_BAO_Cache::getItem('contact fields', $cacheKeyString); - } + $fields = CRM_Utils_Array::value($cacheKeyString, self::$_importableFields) ?: Civi::cache('fields')->get($cacheKeyString); if (!$fields) { $fields = CRM_Contact_DAO_Contact::import(); // get the fields thar are meant for contact types - if (in_array($contactType, array( + if (in_array($contactType, [ 'Individual', 'Household', 'Organization', 'All', - ))) { + ])) { $fields = array_merge($fields, CRM_Core_OptionValue::getFields('', $contactType)); } @@ -1264,14 +1413,14 @@ public static function importableFields( ); //unset the fields, which are not related to their //contact type. - $commonValues = array( - 'Individual' => array( + $commonValues = [ + 'Individual' => [ 'household_name', 'legal_name', 'sic_code', 'organization_name', - ), - 'Household' => array( + ], + 'Household' => [ 'first_name', 'middle_name', 'last_name', @@ -1288,8 +1437,8 @@ public static function importableFields( 'home_URL', 'is_deceased', 'deceased_date', - ), - 'Organization' => array( + ], + 'Organization' => [ 'first_name', 'middle_name', 'last_name', @@ -1302,14 +1451,14 @@ public static function importableFields( 'household_name', 'is_deceased', 'deceased_date', - ), - ); + ], + ]; foreach ($commonValues[$contactType] as $value) { unset($fields[$value]); } } else { - foreach (array('Individual', 'Household', 'Organization') as $type) { + foreach (['Individual', 'Household', 'Organization'] as $type) { $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport($type, $showAll, @@ -1323,42 +1472,42 @@ public static function importableFields( } if ($isProfile) { - $fields = array_merge($fields, array( - 'group' => array( + $fields = array_merge($fields, [ + 'group' => [ 'title' => ts('Group(s)'), 'name' => 'group', - ), - 'tag' => array( + ], + 'tag' => [ 'title' => ts('Tag(s)'), 'name' => 'tag', - ), - 'note' => array( - 'title' => ts('Note(s)'), + ], + 'note' => [ + 'title' => ts('Note'), 'name' => 'note', - ), - 'communication_style_id' => array( + ], + 'communication_style_id' => [ 'title' => ts('Communication Style'), 'name' => 'communication_style_id', - ), - )); + ], + ]); } //Sorting fields in alphabetical order(CRM-1507) $fields = CRM_Utils_Array::crmArraySortByField($fields, 'title'); - CRM_Core_BAO_Cache::setItem($fields, 'contact fields', $cacheKeyString); + Civi::cache('fields')->set($cacheKeyString, $fields); } self::$_importableFields[$cacheKeyString] = $fields; if (!$isProfile) { if (!$status) { - $fields = array_merge(array('do_not_import' => array('title' => ts('- do not import -'))), + $fields = array_merge(['do_not_import' => ['title' => ts('- do not import -')]], self::$_importableFields[$cacheKeyString] ); } else { - $fields = array_merge(array('' => array('title' => ts('- Contact Fields -'))), + $fields = array_merge(['' => ['title' => ts('- Contact Fields -')]], self::$_importableFields[$cacheKeyString] ); } @@ -1380,6 +1529,7 @@ public static function importableFields( * True when used during search, might conflict with export param?. * * @param bool $withMultiRecord + * @param bool $checkPermissions * * @return array * array of exportable Fields @@ -1400,49 +1550,48 @@ public static function &exportableFields($contactType = 'Individual', $status = if (!self::$_exportableFields || !CRM_Utils_Array::value($cacheKeyString, self::$_exportableFields)) { if (!self::$_exportableFields) { - self::$_exportableFields = array(); + self::$_exportableFields = []; } // check if we can retrieve from database cache - $fields = CRM_Core_BAO_Cache::getItem('contact fields', $cacheKeyString); + $fields = Civi::cache('fields')->get($cacheKeyString); if (!$fields) { $fields = CRM_Contact_DAO_Contact::export(); // The fields are meant for contact types. - if (in_array($contactType, array( + if (in_array($contactType, [ 'Individual', 'Household', 'Organization', 'All', - ) - )) { + ])) { $fields = array_merge($fields, CRM_Core_OptionValue::getFields('', $contactType)); } // add current employer for individuals - $fields = array_merge($fields, array( + $fields = array_merge($fields, [ 'current_employer' => - array( + [ 'name' => 'organization_name', 'title' => ts('Current Employer'), - ), - )); + ], + ]); - $locationType = array( - 'location_type' => array( + $locationType = [ + 'location_type' => [ 'name' => 'location_type', 'where' => 'civicrm_location_type.name', 'title' => ts('Location Type'), - ), - ); + ], + ]; - $IMProvider = array( - 'im_provider' => array( + $IMProvider = [ + 'im_provider' => [ 'name' => 'im_provider', 'where' => 'civicrm_im.provider_id', 'title' => ts('IM Provider'), - ), - ); + ], + ]; $locationFields = array_merge($locationType, CRM_Core_DAO_Address::export(), @@ -1481,11 +1630,7 @@ public static function &exportableFields($contactType = 'Individual', $status = ); } else { - foreach (array( - 'Individual', - 'Household', - 'Organization', - ) as $type) { + foreach (['Individual', 'Household', 'Organization'] as $type) { $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport($type, FALSE, FALSE, $search, $checkPermissions, $withMultiRecord) ); @@ -1494,36 +1639,36 @@ public static function &exportableFields($contactType = 'Individual', $status = $fields['current_employer_id']['title'] = ts('Current Employer ID'); //fix for CRM-791 if ($export) { - $fields = array_merge($fields, array( - 'groups' => array( + $fields = array_merge($fields, [ + 'groups' => [ 'title' => ts('Group(s)'), 'name' => 'groups', - ), - 'tags' => array( + ], + 'tags' => [ 'title' => ts('Tag(s)'), 'name' => 'tags', - ), - 'notes' => array( + ], + 'notes' => [ 'title' => ts('Note(s)'), 'name' => 'notes', - ), - )); + ], + ]); } else { - $fields = array_merge($fields, array( - 'group' => array( + $fields = array_merge($fields, [ + 'group' => [ 'title' => ts('Group(s)'), 'name' => 'group', - ), - 'tag' => array( + ], + 'tag' => [ 'title' => ts('Tag(s)'), 'name' => 'tag', - ), - 'note' => array( + ], + 'note' => [ 'title' => ts('Note(s)'), 'name' => 'note', - ), - )); + ], + ]); } //Sorting fields in alphabetical order(CRM-1507) @@ -1534,8 +1679,8 @@ public static function &exportableFields($contactType = 'Individual', $status = $fields = array_merge($sortArray, $fields); //unset the field which are not related to their contact type. if ($contactType != 'All') { - $commonValues = array( - 'Individual' => array( + $commonValues = [ + 'Individual' => [ 'household_name', 'legal_name', 'sic_code', @@ -1543,8 +1688,8 @@ public static function &exportableFields($contactType = 'Individual', $status = 'email_greeting_custom', 'postal_greeting_custom', 'addressee_custom', - ), - 'Household' => array( + ], + 'Household' => [ 'first_name', 'middle_name', 'last_name', @@ -1567,8 +1712,8 @@ public static function &exportableFields($contactType = 'Individual', $status = 'addressee_custom', 'prefix_id', 'suffix_id', - ), - 'Organization' => array( + ], + 'Organization' => [ 'first_name', 'middle_name', 'last_name', @@ -1588,14 +1733,14 @@ public static function &exportableFields($contactType = 'Individual', $status = 'is_deceased', 'deceased_date', 'current_employer', - ), - ); + ], + ]; foreach ($commonValues[$contactType] as $value) { unset($fields[$value]); } } - CRM_Core_BAO_Cache::setItem($fields, 'contact fields', $cacheKeyString); + Civi::cache('fields')->set($cacheKeyString, $fields); } self::$_exportableFields[$cacheKeyString] = $fields; } @@ -1604,7 +1749,7 @@ public static function &exportableFields($contactType = 'Individual', $status = $fields = self::$_exportableFields[$cacheKeyString]; } else { - $fields = array_merge(array('' => array('title' => ts('- Contact Fields -'))), + $fields = array_merge(['' => ['title' => ts('- Contact Fields -')]], self::$_exportableFields[$cacheKeyString] ); } @@ -1623,9 +1768,9 @@ public static function &exportableFields($contactType = 'Individual', $status = * @return array * Contact details */ - public static function getHierContactDetails($contactId, &$fields) { - $params = array(array('contact_id', '=', $contactId, 0, 0)); - $options = array(); + public static function getHierContactDetails($contactId, $fields) { + $params = [['contact_id', '=', $contactId, 0, 0]]; + $options = []; $returnProperties = self::makeHierReturnProperties($fields, $contactId); @@ -1654,9 +1799,9 @@ public static function getHierContactDetails($contactId, &$fields) { public static function &makeHierReturnProperties($fields, $contactId = NULL) { $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $returnProperties = array(); + $returnProperties = []; - $multipleFields = array('website' => 'url'); + $multipleFields = ['website' => 'url']; foreach ($fields as $name => $dontCare) { if (strpos($name, '-') !== FALSE) { list($fieldName, $id, $type) = CRM_Utils_System::explode('-', $name, 3); @@ -1673,19 +1818,19 @@ public static function &makeHierReturnProperties($fields, $contactId = NULL) { } if (empty($returnProperties['location'])) { - $returnProperties['location'] = array(); + $returnProperties['location'] = []; } if (empty($returnProperties['location'][$locationTypeName])) { - $returnProperties['location'][$locationTypeName] = array(); + $returnProperties['location'][$locationTypeName] = []; $returnProperties['location'][$locationTypeName]['location_type'] = $id; } - if (in_array($fieldName, array( + if (in_array($fieldName, [ 'phone', 'im', 'email', 'openid', 'phone_ext', - ))) { + ])) { if ($type) { $returnProperties['location'][$locationTypeName][$fieldName . '-' . $type] = 1; } @@ -1728,7 +1873,7 @@ public static function &makeHierReturnProperties($fields, $contactId = NULL) { */ public static function getPrimaryLocationType($contactId, $skipDefaultPriamry = FALSE, $block = NULL) { if ($block) { - $entityBlock = array('contact_id' => $contactId); + $entityBlock = ['contact_id' => $contactId]; $blocks = CRM_Core_BAO_Location::getValues($entityBlock); foreach ($blocks[$block] as $key => $value) { if (!empty($value['is_primary'])) { @@ -1756,7 +1901,7 @@ public static function getPrimaryLocationType($contactId, $skipDefaultPriamry = LEFT JOIN civicrm_openid ON ( civicrm_openid.is_primary = 1 AND civicrm_openid.contact_id = civicrm_contact.id) WHERE civicrm_contact.id = %1 "; - $params = array(1 => array($contactId, 'Integer')); + $params = [1 => [$contactId, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); @@ -1800,7 +1945,7 @@ public static function getContactDetails($id) { FROM civicrm_contact LEFT JOIN civicrm_email ON (civicrm_contact.id = civicrm_email.contact_id) WHERE civicrm_contact.id = %1 ORDER BY civicrm_email.is_primary DESC"; - $params = array(1 => array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); if ($dao->fetch()) { @@ -1819,9 +1964,9 @@ public static function getContactDetails($id) { $doNotEmail = $dao->do_not_email ? TRUE : FALSE; $onHold = $dao->on_hold ? TRUE : FALSE; $isDeceased = $dao->is_deceased ? TRUE : FALSE; - return array($name, $email, $doNotEmail, $onHold, $isDeceased); + return [$name, $email, $doNotEmail, $onHold, $isDeceased]; } - return array(NULL, NULL, NULL, NULL, NULL); + return [NULL, NULL, NULL, NULL, NULL]; } /** @@ -1847,7 +1992,7 @@ public static function getContactDetails($id) { */ public static function createProfileContact( &$params, - &$fields, + &$fields = [], $contactID = NULL, $addToGroupID = NULL, $ufGroupId = NULL, @@ -1886,11 +2031,11 @@ public static function createProfileContact( $data['is_opt_out'] = $isOptOut; // on change, create new civicrm_subscription_history entry if (($wasOptOut != $isOptOut) && !empty($contactDetails['contact_id'])) { - $shParams = array( + $shParams = [ 'contact_id' => $contactDetails['contact_id'], 'status' => $isOptOut ? 'Removed' : 'Added', 'method' => 'Web', - ); + ]; CRM_Contact_BAO_SubscriptionHistory::create($shParams); } } @@ -1917,19 +2062,21 @@ public static function createProfileContact( CRM_Contact_BAO_GroupContact::create($params['group'], $contactID, $visibility, $method); } - if (!empty($fields['tag'])) { - CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $contactID); + if (!empty($fields['tag']) && array_key_exists('tag', $params)) { + // Convert comma separated form values from select2 v3 + $tags = is_array($params['tag']) ? $params['tag'] : array_fill_keys(array_filter(explode(',', $params['tag'])), 1); + CRM_Core_BAO_EntityTag::create($tags, 'civicrm_contact', $contactID); } //to add profile in default group if (is_array($addToGroupID)) { - $contactIds = array($contactID); + $contactIds = [$contactID]; foreach ($addToGroupID as $groupId) { CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $groupId); } } elseif ($addToGroupID) { - $contactIds = array($contactID); + $contactIds = [$contactID]; CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $addToGroupID); } @@ -1958,14 +2105,14 @@ public static function createProfileContact( */ public static function formatProfileContactParams( &$params, - &$fields, + $fields, $contactID = NULL, $ufGroupId = NULL, $ctype = NULL, $skipCustom = FALSE ) { - $data = $contactDetails = array(); + $data = $contactDetails = []; // get the contact details (hier) if ($contactID) { @@ -2007,11 +2154,17 @@ public static function formatProfileContactParams( ) { // if profile was used, and had any subtype, we obtain it from there //CRM-13596 - add to existing contact types, rather than overwriting - $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']); - if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) { - //CRM-20517 - make sure contact_sub_type gets the correct delimiters - $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR); - $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); + if (empty($data['contact_sub_type'])) { + // If we don't have a contact ID the $data['contact_sub_type'] will not be defined... + $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); + } + else { + $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']); + if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) { + //CRM-20517 - make sure contact_sub_type gets the correct delimiters + $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR); + $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); + } } } @@ -2022,7 +2175,7 @@ public static function formatProfileContactParams( $data['household_name'] = CRM_Utils_Array::value('household_name', $contactDetails); } - $locationType = array(); + $locationType = []; $count = 1; if ($contactID) { @@ -2037,9 +2190,9 @@ public static function formatProfileContactParams( $billingLocationTypeId = CRM_Core_BAO_LocationType::getBilling(); - $blocks = array('email', 'phone', 'im', 'openid'); + $blocks = ['email', 'phone', 'im', 'openid']; - $multiplFields = array('url'); + $multiplFields = ['url']; // prevent overwritten of formatted array, reset all block from // params if it is not in valid format (since import pass valid format) foreach ($blocks as $blk) { @@ -2086,7 +2239,7 @@ public static function formatProfileContactParams( $loc = CRM_Utils_Array::key($index, $locationType); - $blockName = in_array($fieldName, $blocks) ? $fieldName : 'address'; + $blockName = self::getLocationEntityForKey($fieldName); $data[$blockName][$loc]['location_type_id'] = $locTypeId; @@ -2105,7 +2258,7 @@ public static function formatProfileContactParams( $data[$blockName][$loc]['is_primary'] = 1; } - if (in_array($fieldName, array('phone'))) { + if (in_array($fieldName, ['phone'])) { if ($typeId) { $data['phone'][$loc]['phone_type_id'] = $typeId; } @@ -2124,9 +2277,6 @@ public static function formatProfileContactParams( unset($data['phone'][$loc]['is_primary']); } } - elseif ($fieldName == 'phone_ext') { - $data['phone'][$loc]['phone_ext'] = $value; - } elseif ($fieldName == 'email') { $data['email'][$loc]['email'] = $value; if (empty($contactID)) { @@ -2183,7 +2333,7 @@ public static function formatProfileContactParams( $data['address'][$loc][substr($fieldName, 8)] = $value; } else { - $data['address'][$loc][$fieldName] = $value; + $data[$blockName][$loc][$fieldName] = $value; } } } @@ -2252,20 +2402,20 @@ public static function formatProfileContactParams( FALSE ); } - elseif ($key == 'edit') { + elseif ($key === 'edit') { continue; } else { - if ($key == 'location') { + if ($key === 'location') { foreach ($value as $locationTypeId => $field) { foreach ($field as $block => $val) { - if ($block == 'address' && array_key_exists('address_name', $val)) { + if ($block === 'address' && array_key_exists('address_name', $val)) { $value[$locationTypeId][$block]['name'] = $value[$locationTypeId][$block]['address_name']; } } } } - if ($key == 'phone' && isset($params['phone_ext'])) { + if ($key === 'phone' && isset($params['phone_ext'])) { $data[$key] = $value; foreach ($value as $cnt => $phoneBlock) { if ($params[$key][$cnt]['location_type_id'] == $params['phone_ext'][$cnt]['location_type_id']) { @@ -2273,20 +2423,10 @@ public static function formatProfileContactParams( } } } - elseif (in_array($key, - array( - 'nick_name', - 'job_title', - 'middle_name', - 'birth_date', - 'gender_id', - 'current_employer', - 'prefix_id', - 'suffix_id', - )) && - ($value == '' || !isset($value)) && + elseif (in_array($key, ['nick_name', 'job_title', 'middle_name', 'birth_date', 'gender_id', 'current_employer', 'prefix_id', 'suffix_id']) + && ($value == '' || !isset($value)) && ($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 || - ($key == 'current_employer' && empty($params['current_employer']))) { + ($key === 'current_employer' && empty($params['current_employer']))) { // CRM-10128: if auth source is not checksum / login && $value is blank, do not fill $data with empty value // to avoid update with empty values continue; @@ -2319,7 +2459,7 @@ public static function formatProfileContactParams( } } - return array($data, $contactDetails); + return [$data, $contactDetails]; } /** @@ -2359,11 +2499,11 @@ public static function matchContactOnEmail($mail, $ctype = NULL) { $query .= " WHERE civicrm_email.email = %1 AND civicrm_contact.is_deleted=0"; - $p = array(1 => array($mail, 'String')); + $p = [1 => [$mail, 'String']]; if ($ctype) { $query .= " AND civicrm_contact.contact_type = %3"; - $p[3] = array($ctype, 'String'); + $p[3] = [$ctype, 'String']; } $query .= " ORDER BY civicrm_email.is_primary DESC"; @@ -2398,11 +2538,11 @@ public static function matchContactOnOpenId($openId, $ctype = NULL) { FROM civicrm_contact INNER JOIN civicrm_openid ON ( civicrm_contact.id = civicrm_openid.contact_id ) WHERE civicrm_openid.openid = %1"; - $p = array(1 => array($openId, 'String')); + $p = [1 => [$openId, 'String']]; if ($ctype) { $query .= " AND civicrm_contact.contact_type = %3"; - $p[3] = array($ctype, 'String'); + $p[3] = [$ctype, 'String']; } $query .= " ORDER BY civicrm_openid.is_primary DESC"; @@ -2432,14 +2572,13 @@ public static function getPrimaryEmail($contactID) { LEFT JOIN civicrm_email ON ( civicrm_contact.id = civicrm_email.contact_id ) WHERE civicrm_email.is_primary = 1 AND civicrm_contact.id = %1"; - $p = array(1 => array($contactID, 'Integer')); + $p = [1 => [$contactID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $p); $email = NULL; if ($dao->fetch()) { $email = $dao->email; } - $dao->free(); return $email; } @@ -2460,14 +2599,13 @@ public static function getPrimaryOpenId($contactID) { LEFT JOIN civicrm_openid ON ( civicrm_contact.id = civicrm_openid.contact_id ) WHERE civicrm_contact.id = %1 AND civicrm_openid.is_primary = 1"; - $p = array(1 => array($contactID, 'Integer')); + $p = [1 => [$contactID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $p); $openId = NULL; if ($dao->fetch()) { $openId = $dao->openid; } - $dao->free(); return $openId; } @@ -2491,7 +2629,7 @@ public static function getValues(&$params, &$values) { CRM_Core_DAO::storeValues($contact, $values); - $privacy = array(); + $privacy = []; foreach (self::$_commPrefs as $name) { if (isset($contact->$name)) { $privacy[$name] = $contact->$name; @@ -2503,21 +2641,21 @@ public static function getValues(&$params, &$values) { } // communication Prefferance - $preffComm = $comm = array(); + $preffComm = $comm = []; $comm = explode(CRM_Core_DAO::VALUE_SEPARATOR, $contact->preferred_communication_method ); foreach ($comm as $value) { $preffComm[$value] = 1; } - $temp = array('preferred_communication_method' => $contact->preferred_communication_method); + $temp = ['preferred_communication_method' => $contact->preferred_communication_method]; - $names = array( - 'preferred_communication_method' => array( + $names = [ + 'preferred_communication_method' => [ 'newName' => 'preferred_communication_method_display', 'groupName' => 'preferred_communication_method', - ), - ); + ], + ]; // @todo This can be figured out from metadata & we can avoid the uncached query. CRM_Core_OptionGroup::lookupValues($temp, $names, FALSE); @@ -2574,15 +2712,15 @@ public static function getCountComponent($component, $contactId, $tableName = NU case 'rel': $result = CRM_Contact_BAO_Relationship::getRelationship($contactId, CRM_Contact_BAO_Relationship::CURRENT, - 0, 0, 0, + 0, 1, 0, NULL, NULL, TRUE ); - return count($result); + return $result; case 'group': - return CRM_Contact_BAO_GroupContact::getContactGroup($contactId, "Added", NULL, TRUE); + return CRM_Contact_BAO_GroupContact::getContactGroup($contactId, "Added", NULL, TRUE, FALSE, FALSE, TRUE, NULL, TRUE); case 'log': if (CRM_Core_BAO_Log::useLoggingReport()) { @@ -2612,16 +2750,16 @@ public static function getCountComponent($component, $contactId, $tableName = NU return CRM_Grant_BAO_Grant::getContactGrantCount($contactId); case 'activity': - $input = array( + $input = [ 'contact_id' => $contactId, 'admin' => FALSE, 'caseId' => NULL, 'context' => 'activity', - ); - return CRM_Activity_BAO_Activity::deprecatedGetActivitiesCount($input); + ]; + return CRM_Activity_BAO_Activity::getActivitiesCount($input); case 'mailing': - $params = array('contact_id' => $contactId); + $params = ['contact_id' => $contactId]; return CRM_Mailing_BAO_Mailing::getContactMailingsCount($params); default: @@ -2636,36 +2774,58 @@ public static function getCountComponent($component, $contactId, $tableName = NU } } + /** + * Update contact greetings if an update has resulted in a custom field change. + * + * @param array $updatedFields + * Array of fields that have been updated e.g array('first_name', 'prefix_id', 'custom_2'); + * @param array $contactParams + * Parameters known about the contact. At minimum array('contact_id' => x). + * Fields in this array will take precedence over DB fields (so far only + * in the case of greeting id fields). + */ + public static function updateGreetingsOnTokenFieldChange($updatedFields, $contactParams) { + $contactID = $contactParams['contact_id']; + CRM_Contact_BAO_Contact::ensureGreetingParamsAreSet($contactParams); + $tokens = CRM_Contact_BAO_Contact_Utils::getTokensRequiredForContactGreetings($contactParams); + if (!empty($tokens['all']['contact'])) { + $affectedTokens = array_intersect_key($updatedFields[$contactID], array_flip($tokens['all']['contact'])); + if (!empty($affectedTokens)) { + // @todo this is still reloading the whole contact -fix to be more selective & use pre-loaded. + $contact = new CRM_Contact_BAO_Contact(); + $contact->id = $contactID; + CRM_Contact_BAO_Contact::processGreetings($contact); + } + } + } + /** * Process greetings and cache. * * @param object $contact * Contact object after save. - * @param bool $useDefaults - * Use default greeting values. */ - public static function processGreetings(&$contact, $useDefaults = FALSE) { - if ($useDefaults) { - //retrieve default greetings - $defaultGreetings = CRM_Core_PseudoConstant::greetingDefaults(); - $contactDefaults = $defaultGreetings[$contact->contact_type]; - } + public static function processGreetings(&$contact) { + + //@todo this function does a lot of unnecessary loading. + // ensureGreetingParamsAreSet now makes sure that the contact is + // loaded and using updateGreetingsOnTokenFieldChange + // allows us the possibility of only doing an update if required. - // note that contact object not always has required greeting related - // fields that are required to calculate greeting and - // also other fields used in tokens etc, - // hence we need to retrieve it again. + // The contact object has not always required the + // fields that are required to calculate greetings + // so we need to retrieve it again. if ($contact->_query !== FALSE) { $contact->find(TRUE); } // store object values to an array - $contactDetails = array(); + $contactDetails = []; CRM_Core_DAO::storeValues($contact, $contactDetails); - $contactDetails = array(array($contact->id => $contactDetails)); + $contactDetails = [[$contact->id => $contactDetails]]; $emailGreetingString = $postalGreetingString = $addresseeString = NULL; - $updateQueryString = array(); + $updateQueryString = []; //cache email and postal greeting to greeting display if ($contact->email_greeting_custom != 'null' && $contact->email_greeting_custom) { @@ -2673,24 +2833,17 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { } elseif ($contact->email_greeting_id != 'null' && $contact->email_greeting_id) { // the filter value for Individual contact type is set to 1 - $filter = array( + $filter = [ 'contact_type' => $contact->contact_type, 'greeting_type' => 'email_greeting', - ); + ]; $emailGreeting = CRM_Core_PseudoConstant::greeting($filter); $emailGreetingString = $emailGreeting[$contact->email_greeting_id]; $updateQueryString[] = " email_greeting_custom = NULL "; } else { - if ($useDefaults) { - reset($contactDefaults['email_greeting']); - $emailGreetingID = key($contactDefaults['email_greeting']); - $emailGreetingString = $contactDefaults['email_greeting'][$emailGreetingID]; - $updateQueryString[] = " email_greeting_id = $emailGreetingID "; - $updateQueryString[] = " email_greeting_custom = NULL "; - } - elseif ($contact->email_greeting_custom) { + if ($contact->email_greeting_custom) { $updateQueryString[] = " email_greeting_display = NULL "; } } @@ -2706,27 +2859,20 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { } //postal greetings - if ($contact->postal_greeting_custom != 'null' && $contact->postal_greeting_custom) { + if ($contact->postal_greeting_custom !== 'null' && $contact->postal_greeting_custom) { $postalGreetingString = $contact->postal_greeting_custom; } - elseif ($contact->postal_greeting_id != 'null' && $contact->postal_greeting_id) { - $filter = array( + elseif ($contact->postal_greeting_id !== 'null' && $contact->postal_greeting_id) { + $filter = [ 'contact_type' => $contact->contact_type, 'greeting_type' => 'postal_greeting', - ); + ]; $postalGreeting = CRM_Core_PseudoConstant::greeting($filter); $postalGreetingString = $postalGreeting[$contact->postal_greeting_id]; $updateQueryString[] = " postal_greeting_custom = NULL "; } else { - if ($useDefaults) { - reset($contactDefaults['postal_greeting']); - $postalGreetingID = key($contactDefaults['postal_greeting']); - $postalGreetingString = $contactDefaults['postal_greeting'][$postalGreetingID]; - $updateQueryString[] = " postal_greeting_id = $postalGreetingID "; - $updateQueryString[] = " postal_greeting_custom = NULL "; - } - elseif ($contact->postal_greeting_custom) { + if ($contact->postal_greeting_custom) { $updateQueryString[] = " postal_greeting_display = NULL "; } } @@ -2742,28 +2888,21 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { } // addressee - if ($contact->addressee_custom != 'null' && $contact->addressee_custom) { + if ($contact->addressee_custom !== 'null' && $contact->addressee_custom) { $addresseeString = $contact->addressee_custom; } - elseif ($contact->addressee_id != 'null' && $contact->addressee_id) { - $filter = array( + elseif ($contact->addressee_id !== 'null' && $contact->addressee_id) { + $filter = [ 'contact_type' => $contact->contact_type, 'greeting_type' => 'addressee', - ); + ]; $addressee = CRM_Core_PseudoConstant::greeting($filter); $addresseeString = $addressee[$contact->addressee_id]; $updateQueryString[] = " addressee_custom = NULL "; } else { - if ($useDefaults) { - reset($contactDefaults['addressee']); - $addresseeID = key($contactDefaults['addressee']); - $addresseeString = $contactDefaults['addressee'][$addresseeID]; - $updateQueryString[] = " addressee_id = $addresseeID "; - $updateQueryString[] = " addressee_custom = NULL "; - } - elseif ($contact->addressee_custom) { + if ($contact->addressee_custom) { $updateQueryString[] = " addressee_display = NULL "; } } @@ -2799,13 +2938,13 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { * @return array * loc block ids which fulfill condition. */ - public static function getLocBlockIds($contactId, $criteria = array(), $condOperator = 'AND') { - $locBlockIds = array(); + public static function getLocBlockIds($contactId, $criteria = [], $condOperator = 'AND') { + $locBlockIds = []; if (!$contactId) { return $locBlockIds; } - foreach (array('Email', 'OpenID', 'Phone', 'Address', 'IM') as $block) { + foreach (['Email', 'OpenID', 'Phone', 'Address', 'IM'] as $block) { $name = strtolower($block); $className = "CRM_Core_DAO_$block"; $blockDAO = new $className(); @@ -2813,12 +2952,12 @@ public static function getLocBlockIds($contactId, $criteria = array(), $condOper // build the condition. if (is_array($criteria)) { $fields = $blockDAO->fields(); - $conditions = array(); + $conditions = []; foreach ($criteria as $field => $value) { if (array_key_exists($field, $fields)) { $cond = "( $field = $value )"; // value might be zero or null. - if (!$value || strtolower($value) == 'null') { + if (!$value || strtolower($value) === 'null') { $cond = "( $field = 0 OR $field IS NULL )"; } $conditions[] = $cond; @@ -2834,7 +2973,6 @@ public static function getLocBlockIds($contactId, $criteria = array(), $condOper while ($blockDAO->fetch()) { $locBlockIds[$name][] = $blockDAO->id; } - $blockDAO->free(); } return $locBlockIds; @@ -2849,31 +2987,31 @@ public static function getLocBlockIds($contactId, $criteria = array(), $condOper * Array of context menu for logged in user. */ public static function contextMenu($contactId = NULL) { - $menu = array( - 'view' => array( + $menu = [ + 'view' => [ 'title' => ts('View Contact'), 'weight' => 0, 'ref' => 'view-contact', 'class' => 'no-popup', 'key' => 'view', - 'permissions' => array('view all contacts'), - ), - 'add' => array( + 'permissions' => ['view all contacts'], + ], + 'add' => [ 'title' => ts('Edit Contact'), 'weight' => 0, 'ref' => 'edit-contact', 'class' => 'no-popup', 'key' => 'add', - 'permissions' => array('edit all contacts'), - ), - 'delete' => array( + 'permissions' => ['edit all contacts'], + ], + 'delete' => [ 'title' => ts('Delete Contact'), 'weight' => 0, 'ref' => 'delete-contact', 'key' => 'delete', - 'permissions' => array('access deleted contacts', 'delete contacts'), - ), - 'contribution' => array( + 'permissions' => ['access deleted contacts', 'delete contacts'], + ], + 'contribution' => [ 'title' => ts('Add Contribution'), 'weight' => 5, 'ref' => 'new-contribution', @@ -2883,12 +3021,12 @@ public static function contextMenu($contactId = NULL) { 'href' => CRM_Utils_System::url('civicrm/contact/view/contribution', 'reset=1&action=add&context=contribution' ), - 'permissions' => array( + 'permissions' => [ 'access CiviContribute', 'edit contributions', - ), - ), - 'participant' => array( + ], + ], + 'participant' => [ 'title' => ts('Register for Event'), 'weight' => 10, 'ref' => 'new-participant', @@ -2896,19 +3034,19 @@ public static function contextMenu($contactId = NULL) { 'tab' => 'participant', 'component' => 'CiviEvent', 'href' => CRM_Utils_System::url('civicrm/contact/view/participant', 'reset=1&action=add&context=participant'), - 'permissions' => array( + 'permissions' => [ 'access CiviEvent', 'edit event participants', - ), - ), - 'activity' => array( + ], + ], + 'activity' => [ 'title' => ts('Record Activity'), 'weight' => 35, 'ref' => 'new-activity', 'key' => 'activity', - 'permissions' => array('edit all contacts'), - ), - 'pledge' => array( + 'permissions' => ['edit all contacts'], + ], + 'pledge' => [ 'title' => ts('Add Pledge'), 'weight' => 15, 'ref' => 'new-pledge', @@ -2918,12 +3056,12 @@ public static function contextMenu($contactId = NULL) { 'reset=1&action=add&context=pledge' ), 'component' => 'CiviPledge', - 'permissions' => array( + 'permissions' => [ 'access CiviPledge', 'edit pledges', - ), - ), - 'membership' => array( + ], + ], + 'membership' => [ 'title' => ts('Add Membership'), 'weight' => 20, 'ref' => 'new-membership', @@ -2933,12 +3071,12 @@ public static function contextMenu($contactId = NULL) { 'href' => CRM_Utils_System::url('civicrm/contact/view/membership', 'reset=1&action=add&context=membership' ), - 'permissions' => array( + 'permissions' => [ 'access CiviMember', 'edit memberships', - ), - ), - 'case' => array( + ], + ], + 'case' => [ 'title' => ts('Add Case'), 'weight' => 25, 'ref' => 'new-case', @@ -2946,9 +3084,9 @@ public static function contextMenu($contactId = NULL) { 'tab' => 'case', 'component' => 'CiviCase', 'href' => CRM_Utils_System::url('civicrm/case/add', 'reset=1&action=add&context=case'), - 'permissions' => array('add cases'), - ), - 'grant' => array( + 'permissions' => ['add cases'], + ], + 'grant' => [ 'title' => ts('Add Grant'), 'weight' => 26, 'ref' => 'new-grant', @@ -2958,9 +3096,9 @@ public static function contextMenu($contactId = NULL) { 'href' => CRM_Utils_System::url('civicrm/contact/view/grant', 'reset=1&action=add&context=grant' ), - 'permissions' => array('edit grants'), - ), - 'rel' => array( + 'permissions' => ['edit grants'], + ], + 'rel' => [ 'title' => ts('Add Relationship'), 'weight' => 30, 'ref' => 'new-relationship', @@ -2969,9 +3107,9 @@ public static function contextMenu($contactId = NULL) { 'href' => CRM_Utils_System::url('civicrm/contact/view/rel', 'reset=1&action=add' ), - 'permissions' => array('edit all contacts'), - ), - 'note' => array( + 'permissions' => ['edit all contacts'], + ], + 'note' => [ 'title' => ts('Add Note'), 'weight' => 40, 'ref' => 'new-note', @@ -2981,35 +3119,35 @@ public static function contextMenu($contactId = NULL) { 'href' => CRM_Utils_System::url('civicrm/contact/view/note', 'reset=1&action=add' ), - 'permissions' => array('edit all contacts'), - ), - 'email' => array( + 'permissions' => ['edit all contacts'], + ], + 'email' => [ 'title' => ts('Send an Email'), 'weight' => 45, 'ref' => 'new-email', 'key' => 'email', - 'permissions' => array('view all contacts'), - ), - 'group' => array( + 'permissions' => ['view all contacts'], + ], + 'group' => [ 'title' => ts('Add to Group'), 'weight' => 50, 'ref' => 'group-add-contact', 'key' => 'group', 'tab' => 'group', - 'permissions' => array('edit groups'), - ), - 'tag' => array( + 'permissions' => ['edit groups'], + ], + 'tag' => [ 'title' => ts('Tag Contact'), 'weight' => 55, 'ref' => 'tag-contact', 'key' => 'tag', 'tab' => 'tag', - 'permissions' => array('edit all contacts'), - ), - ); + 'permissions' => ['edit all contacts'], + ], + ]; - $menu['otherActions'] = array( - 'print' => array( + $menu['otherActions'] = [ + 'print' => [ 'title' => ts('Print Summary'), 'description' => ts('Printer-friendly view of this page.'), 'weight' => 5, @@ -3021,8 +3159,8 @@ public static function contextMenu($contactId = NULL) { ), 'class' => 'print', 'icon' => 'crm-i fa-print', - ), - 'vcard' => array( + ], + 'vcard' => [ 'title' => ts('vCard'), 'description' => ts('vCard record for this contact.'), 'weight' => 10, @@ -3034,11 +3172,11 @@ public static function contextMenu($contactId = NULL) { ), 'class' => 'vcard', 'icon' => 'crm-i fa-list-alt', - ), - ); + ], + ]; if (CRM_Core_Permission::check('access Contact Dashboard')) { - $menu['otherActions']['dashboard'] = array( + $menu['otherActions']['dashboard'] = [ 'title' => ts('Contact Dashboard'), 'description' => ts('Contact Dashboard'), 'weight' => 15, @@ -3050,12 +3188,12 @@ public static function contextMenu($contactId = NULL) { // as CRM_Core_Config::singleton()->userSystem->getUserRecordUrl($contactId) 'href' => CRM_Utils_System::url('civicrm/user', "reset=1&id={$contactId}"), 'icon' => 'crm-i fa-tachometer', - ); + ]; } $uid = CRM_Core_BAO_UFMatch::getUFId($contactId); if ($uid) { - $menu['otherActions']['user-record'] = array( + $menu['otherActions']['user-record'] = [ 'title' => ts('User Record'), 'description' => ts('User Record'), 'weight' => 20, @@ -3065,10 +3203,10 @@ public static function contextMenu($contactId = NULL) { 'class' => 'user-record', 'href' => CRM_Core_Config::singleton()->userSystem->getUserRecordUrl($contactId), 'icon' => 'crm-i fa-user', - ); + ]; } elseif (CRM_Core_Config::singleton()->userSystem->checkPermissionAddUser()) { - $menu['otherActions']['user-add'] = array( + $menu['otherActions']['user-add'] = [ 'title' => ts('Create User Record'), 'description' => ts('Create User Record'), 'weight' => 25, @@ -3078,7 +3216,7 @@ public static function contextMenu($contactId = NULL) { 'class' => 'user-add', 'href' => CRM_Utils_System::url('civicrm/contact/view/useradd', 'reset=1&action=add&cid=' . $contactId), 'icon' => 'crm-i fa-user-plus', - ); + ]; } CRM_Utils_Hook::summaryActions($menu, $contactId); @@ -3087,7 +3225,7 @@ public static function contextMenu($contactId = NULL) { //3. check for acls. //3. edit and view contact are directly accessible to user. - $aclPermissionedTasks = array( + $aclPermissionedTasks = [ 'view-contact', 'edit-contact', 'new-activity', @@ -3095,40 +3233,40 @@ public static function contextMenu($contactId = NULL) { 'group-add-contact', 'tag-contact', 'delete-contact', - ); + ]; $corePermission = CRM_Core_Permission::getPermission(); - $contextMenu = array(); + $contextMenu = []; foreach ($menu as $key => $values) { - if ($key != 'otherActions') { + if ($key !== 'otherActions') { // user does not have necessary permissions. if (!self::checkUserMenuPermissions($aclPermissionedTasks, $corePermission, $values)) { continue; } // build directly accessible action menu. - if (in_array($values['ref'], array( + if (in_array($values['ref'], [ 'view-contact', 'edit-contact', - ))) { - $contextMenu['primaryActions'][$key] = array( + ])) { + $contextMenu['primaryActions'][$key] = [ 'title' => $values['title'], 'ref' => $values['ref'], 'class' => CRM_Utils_Array::value('class', $values), 'key' => $values['key'], - ); + ]; continue; } // finally get menu item for -more- action widget. - $contextMenu['moreActions'][$values['weight']] = array( + $contextMenu['moreActions'][$values['weight']] = [ 'title' => $values['title'], 'ref' => $values['ref'], 'href' => CRM_Utils_Array::value('href', $values), 'tab' => CRM_Utils_Array::value('tab', $values), 'class' => CRM_Utils_Array::value('class', $values), 'key' => $values['key'], - ); + ]; } else { foreach ($values as $value) { @@ -3138,7 +3276,7 @@ public static function contextMenu($contactId = NULL) { } // finally get menu item for -more- action widget. - $contextMenu['otherActions'][$value['weight']] = array( + $contextMenu['otherActions'][$value['weight']] = [ 'title' => $value['title'], 'ref' => $value['ref'], 'href' => CRM_Utils_Array::value('href', $value), @@ -3146,7 +3284,7 @@ public static function contextMenu($contactId = NULL) { 'class' => CRM_Utils_Array::value('class', $value), 'icon' => CRM_Utils_Array::value('icon', $value), 'key' => $value['key'], - ); + ]; } } } @@ -3200,15 +3338,15 @@ public static function checkUserMenuPermissions($aclPermissionedTasks, $corePerm } // if still user does not have required permissions, check acl. - if (!$hasAllPermissions && $menuOptions['ref'] != 'delete-contact') { + if (!$hasAllPermissions && $menuOptions['ref'] !== 'delete-contact') { if (in_array($menuOptions['ref'], $aclPermissionedTasks) && - $corePermission == CRM_Core_Permission::EDIT - ) { + $corePermission == CRM_Core_Permission::EDIT + ) { $hasAllPermissions = TRUE; } - elseif (in_array($menuOptions['ref'], array( + elseif (in_array($menuOptions['ref'], [ 'new-email', - ))) { + ])) { // grant permissions for these tasks. $hasAllPermissions = TRUE; } @@ -3226,30 +3364,21 @@ public static function checkUserMenuPermissions($aclPermissionedTasks, $corePerm * @param int $masterAddressId * Master id. * @param int $contactId - * Contact id. + * Contact id. (deprecated - do not use) * * @return string|null * the found display name or null. */ public static function getMasterDisplayName($masterAddressId = NULL, $contactId = NULL) { $masterDisplayName = NULL; - $sql = NULL; - if (!$masterAddressId && !$contactId) { + if (!$masterAddressId) { return $masterDisplayName; } - if ($masterAddressId) { - $sql = " + $sql = " SELECT display_name from civicrm_contact LEFT JOIN civicrm_address ON ( civicrm_address.contact_id = civicrm_contact.id ) WHERE civicrm_address.id = " . $masterAddressId; - } - elseif ($contactId) { - $sql = " - SELECT display_name from civicrm_contact cc, civicrm_address add1 -LEFT JOIN civicrm_address add2 ON ( add1.master_id = add2.id ) - WHERE cc.id = add2.contact_id AND add1.contact_id = " . $contactId; - } $masterDisplayName = CRM_Core_DAO::singleValueQuery($sql); return $masterDisplayName; @@ -3268,75 +3397,31 @@ public static function getTimestamps($contactId) { 'SELECT created_date, modified_date FROM civicrm_contact WHERE id = %1', - array( - 1 => array($contactId, 'Integer'), - ) + [ + 1 => [$contactId, 'Integer'], + ] ); if ($timestamps->fetch()) { - return array( + return [ 'created_date' => $timestamps->created_date, 'modified_date' => $timestamps->modified_date, - ); + ]; } else { return NULL; } } - /** - * Generate triggers to update the timestamp. - * - * The corresponding civicrm_contact row is updated on insert/update/delete - * to a table that extends civicrm_contact. - * Don't regenerate triggers for all such tables if only asked for one table. - * - * @param array $info - * Reference to the array where generated trigger information is being stored - * @param string|null $reqTableName - * Name of the table for which triggers are being generated, or NULL if all tables - * @param array $relatedTableNames - * Array of all core or all custom table names extending civicrm_contact - * @param string $contactRefColumn - * 'contact_id' if processing core tables, 'entity_id' if processing custom tables - * - * @link https://issues.civicrm.org/jira/browse/CRM-15602 - * @see triggerInfo - */ - public static function generateTimestampTriggers(&$info, $reqTableName, $relatedTableNames, $contactRefColumn) { - // Safety - $contactRefColumn = CRM_Core_DAO::escapeString($contactRefColumn); - // If specific related table requested, just process that one - if (in_array($reqTableName, $relatedTableNames)) { - $relatedTableNames = array($reqTableName); - } - - // If no specific table requested (include all related tables), - // or a specific related table requested (as matched above) - if (empty($reqTableName) || in_array($reqTableName, $relatedTableNames)) { - $info[] = array( - 'table' => $relatedTableNames, - 'when' => 'AFTER', - 'event' => array('INSERT', 'UPDATE'), - 'sql' => "\nUPDATE civicrm_contact SET modified_date = CURRENT_TIMESTAMP WHERE id = NEW.$contactRefColumn;\n", - ); - $info[] = array( - 'table' => $relatedTableNames, - 'when' => 'AFTER', - 'event' => array('DELETE'), - 'sql' => "\nUPDATE civicrm_contact SET modified_date = CURRENT_TIMESTAMP WHERE id = OLD.$contactRefColumn;\n", - ); - } - } - /** * Get a list of triggers for the contact table. * - * @see hook_civicrm_triggerInfo - * @see CRM_Core_DAO::triggerRebuild - * @see http://issues.civicrm.org/jira/browse/CRM-10554 - * * @param $info * @param null $tableName + * + * @see http://issues.civicrm.org/jira/browse/CRM-10554 + * + * @see hook_civicrm_triggerInfo + * @see CRM_Core_DAO::triggerRebuild */ public static function triggerInfo(&$info, $tableName = NULL) { //during upgrade, first check for valid version and then create triggers @@ -3349,37 +3434,16 @@ public static function triggerInfo(&$info, $tableName = NULL) { } } - // Update timestamp for civicrm_contact itself - if ($tableName == NULL || $tableName == self::getTableName()) { - $info[] = array( - 'table' => array(self::getTableName()), - 'when' => 'BEFORE', - 'event' => array('INSERT'), - 'sql' => "\nSET NEW.created_date = CURRENT_TIMESTAMP;\n", - ); - } - - // Update timestamp when modifying closely related core tables - $relatedTables = array( - 'civicrm_address', - 'civicrm_email', - 'civicrm_im', - 'civicrm_phone', - 'civicrm_website', - ); - self::generateTimestampTriggers($info, $tableName, $relatedTables, 'contact_id'); - - // Update timestamp when modifying related custom-data tables - $customGroupTables = array(); - $customGroupDAO = CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity('Contact'); - $customGroupDAO->is_multiple = 0; - $customGroupDAO->find(); - while ($customGroupDAO->fetch()) { - $customGroupTables[] = $customGroupDAO->table_name; - } - if (!empty($customGroupTables)) { - self::generateTimestampTriggers($info, $tableName, $customGroupTables, 'entity_id'); - } + // Modifications to these records should update the contact timestamps. + \Civi\Core\SqlTrigger\TimestampTriggers::create('civicrm_contact', 'Contact') + ->setRelations([ + ['table' => 'civicrm_address', 'column' => 'contact_id'], + ['table' => 'civicrm_email', 'column' => 'contact_id'], + ['table' => 'civicrm_im', 'column' => 'contact_id'], + ['table' => 'civicrm_phone', 'column' => 'contact_id'], + ['table' => 'civicrm_website', 'column' => 'contact_id'], + ]) + ->alterTriggerInfo($info, $tableName); // Update phone table to populate phone_numeric field if (!$tableName || $tableName == 'civicrm_phone') { @@ -3387,12 +3451,12 @@ public static function triggerInfo(&$info, $tableName = NULL) { $sqlTriggers = Civi::service('sql_triggers'); $sqlTriggers->enqueueQuery(self::DROP_STRIP_FUNCTION_43); $sqlTriggers->enqueueQuery(self::CREATE_STRIP_FUNCTION_43); - $info[] = array( - 'table' => array('civicrm_phone'), + $info[] = [ + 'table' => ['civicrm_phone'], 'when' => 'BEFORE', - 'event' => array('INSERT', 'UPDATE'), + 'event' => ['INSERT', 'UPDATE'], 'sql' => "\nSET NEW.phone_numeric = civicrm_strip_non_numeric(NEW.phone);\n", - ); + ]; } } @@ -3422,21 +3486,21 @@ public static function checkDomainContact($contactId) { /** * Get options for a given contact field. * - * @see CRM_Core_DAO::buildOptions - * - * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow? - * TODO: In context of chainselect, what to return if e.g. a country has no states? - * * @param string $fieldName * @param string $context - * @see CRM_Core_DAO::buildOptionsContext * @param array $props * whatever is known about this dao object. * * @return array|bool + * @see CRM_Core_DAO::buildOptions + * + * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow? + * TODO: In context of chainselect, what to return if e.g. a country has no states? + * + * @see CRM_Core_DAO::buildOptionsContext */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { - $params = array(); + public static function buildOptions($fieldName, $context = NULL, $props = []) { + $params = []; // Special logic for fields whose options depend on context or properties switch ($fieldName) { case 'contact_sub_type': @@ -3485,6 +3549,7 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( * * @param string $type * @param int $id + * * @return bool */ public static function deleteObjectWithPrimary($type, $id) { @@ -3495,8 +3560,9 @@ public static function deleteObjectWithPrimary($type, $id) { $obj = new $daoName(); $obj->id = $id; $obj->find(); + $hookParams = []; if ($obj->fetch()) { - CRM_Utils_Hook::pre('delete', $type, $id, CRM_Core_DAO::$_nullArray); + CRM_Utils_Hook::pre('delete', $type, $id, $hookParams); $contactId = $obj->contact_id; $obj->delete(); } @@ -3517,10 +3583,8 @@ public static function deleteObjectWithPrimary($type, $id) { $dao->save(); } } - $dao->free(); } CRM_Utils_Hook::post('delete', $type, $id, $obj); - $obj->free(); return TRUE; } @@ -3530,10 +3594,10 @@ public static function deleteObjectWithPrimary($type, $id) { public function addSelectWhereClause() { // We always return an array with these keys, even if they are empty, // because this tells the query builder that we have considered these fields for acls - $clauses = array( + $clauses = [ 'id' => (array) CRM_Contact_BAO_Contact_Permission::cacheSubquery(), - 'is_deleted' => CRM_Core_Permission::check('access deleted contacts') ? array() : array('!= 1'), - ); + 'is_deleted' => CRM_Core_Permission::check('access deleted contacts') ? [] : ['!= 1'], + ]; CRM_Utils_Hook::selectWhereClause($this, $clauses); return $clauses; } @@ -3552,14 +3616,25 @@ public function addSelectWhereClause() { * @param bool $checkPermissions * @param int $ruleGroupID * ID of the rule group to be used if an override is desirable. + * @param array $contextParams + * The context if relevant, eg. ['event_id' => X] * * @return array */ - public static function getDuplicateContacts($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = array(), $checkPermissions = TRUE, $ruleGroupID = NULL) { + public static function getDuplicateContacts($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = [], $checkPermissions = TRUE, $ruleGroupID = NULL, $contextParams = []) { $dedupeParams = CRM_Dedupe_Finder::formatParams($input, $contactType); $dedupeParams['check_permission'] = $checkPermissions; - $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, $rule, $excludedContactIDs, $ruleGroupID); - return $ids; + $dedupeParams['contact_type'] = $contactType; + $dedupeParams['rule'] = $rule; + $dedupeParams['rule_group_id'] = $ruleGroupID; + $dedupeParams['excluded_contact_ids'] = $excludedContactIDs; + $dedupeResults['ids'] = []; + $dedupeResults['handled'] = FALSE; + CRM_Utils_Hook::findDuplicates($dedupeParams, $dedupeResults, $contextParams); + if (!$dedupeResults['handled']) { + $dedupeResults['ids'] = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, $rule, $excludedContactIDs, $ruleGroupID); + } + return $dedupeResults['ids']; } /** @@ -3576,15 +3651,104 @@ public static function getDuplicateContacts($input, $contactType, $rule = 'Unsup * @param bool $checkPermissions * @param int $ruleGroupID * ID of the rule group to be used if an override is desirable. + * @param array $contextParams + * The context if relevant, eg. ['event_id' => X] * - * @return int|NULL + * @return int|null */ - public static function getFirstDuplicateContact($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = array(), $checkPermissions = TRUE, $ruleGroupID = NULL) { - $ids = self::getDuplicateContacts($input, $contactType, $rule, $excludedContactIDs, $checkPermissions, $ruleGroupID); + public static function getFirstDuplicateContact($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = [], $checkPermissions = TRUE, $ruleGroupID = NULL, $contextParams = []) { + $ids = self::getDuplicateContacts($input, $contactType, $rule, $excludedContactIDs, $checkPermissions, $ruleGroupID, $contextParams); if (empty($ids)) { return NULL; } return $ids[0]; } + /** + * Check if a field is associated with an entity that has a location type. + * + * (ie. is an address, phone, email etc field). + * + * @param string $fieldTitle + * Title of the field (not the name - create a new function for that if required). + * + * @return bool + */ + public static function isFieldHasLocationType($fieldTitle) { + foreach (CRM_Contact_BAO_Contact::importableFields() as $key => $field) { + if ($field['title'] === $fieldTitle) { + return CRM_Utils_Array::value('hasLocationType', $field); + } + } + return FALSE; + } + + /** + * @param array $appendProfiles + * Name of profile(s) to append to each link. + * + * @return array + */ + public static function getEntityRefCreateLinks($appendProfiles = []) { + // You'd think that "create contacts" would be the permission to check, + // But new contact popups are profile forms and those use their own permissions. + if (!CRM_Core_Permission::check([['profile create', 'profile listings and forms']])) { + return FALSE; + } + $profiles = []; + foreach (CRM_Contact_BAO_ContactType::basicTypes() as $contactType) { + $profiles[] = 'new_' . strtolower($contactType); + } + $retrieved = civicrm_api3('uf_group', 'get', [ + 'name' => ['IN' => array_merge($profiles, (array) $appendProfiles)], + 'is_active' => 1, + ]); + $links = $append = []; + if (!empty($retrieved['values'])) { + $icons = [ + 'individual' => 'fa-user', + 'organization' => 'fa-building', + 'household' => 'fa-home', + ]; + foreach ($retrieved['values'] as $id => $profile) { + if (in_array($profile['name'], $profiles)) { + $links[] = [ + 'label' => $profile['title'], + 'url' => CRM_Utils_System::url('civicrm/profile/create', "reset=1&context=dialog&gid=$id", + NULL, NULL, FALSE, FALSE, TRUE), + 'type' => ucfirst(str_replace('new_', '', $profile['name'])), + 'icon' => CRM_Utils_Array::value(str_replace('new_', '', $profile['name']), $icons), + ]; + } + else { + $append[] = $id; + } + } + foreach ($append as $id) { + foreach ($links as &$link) { + $link['url'] .= ",$id"; + } + } + } + return $links; + } + + /** + * @return array + */ + public static function getEntityRefFilters() { + return [ + ['key' => 'contact_type', 'value' => ts('Contact Type')], + ['key' => 'group', 'value' => ts('Group'), 'entity' => 'GroupContact'], + ['key' => 'tag', 'value' => ts('Tag'), 'entity' => 'EntityTag'], + ['key' => 'state_province', 'value' => ts('State/Province'), 'entity' => 'Address'], + ['key' => 'country', 'value' => ts('Country'), 'entity' => 'Address'], + ['key' => 'gender_id', 'value' => ts('Gender'), 'condition' => ['contact_type' => 'Individual']], + ['key' => 'is_deceased', 'value' => ts('Deceased'), 'condition' => ['contact_type' => 'Individual']], + ['key' => 'contact_id', 'value' => ts('Contact ID'), 'type' => 'text'], + ['key' => 'external_identifier', 'value' => ts('External ID'), 'type' => 'text'], + ['key' => 'source', 'value' => ts('Contact Source'), 'type' => 'text'], + ]; + } + } diff --git a/CRM/Contact/BAO/Contact/Location.php b/CRM/Contact/BAO/Contact/Location.php index 7405537b4445..4d7c24eaa233 100644 --- a/CRM/Contact/BAO/Contact/Location.php +++ b/CRM/Contact/BAO/Contact/Location.php @@ -1,9 +1,9 @@ $id, + 'return' => array('display_name', 'email.email'), + 'api.Email.get' => array( + 'location_type_id' => $locationTypeID, + 'sequential' => 0, + 'return' => array('email', 'location_type_id', 'id'), + ), + ); if ($isPrimary) { - $primaryClause = " AND civicrm_email.is_primary = 1"; + $params['api.Email.get']['is_primary'] = 1; } - $locationClause = NULL; - if ($locationTypeID) { - $locationClause = " AND civicrm_email.location_type_id = $locationTypeID"; + $contacts = civicrm_api3('Contact', 'get', $params); + if ($contacts['count'] > 0) { + $contact = reset($contacts['values']); + if ($contact['api.Email.get']['count'] > 0) { + $email = reset($contact['api.Email.get']['values']); + } } - - $sql = " -SELECT civicrm_contact.display_name, - civicrm_email.email, - civicrm_email.location_type_id, - civicrm_email.id -FROM civicrm_contact -LEFT JOIN civicrm_email ON ( civicrm_contact.id = civicrm_email.contact_id {$primaryClause} {$locationClause} ) -WHERE civicrm_contact.id = %1"; - - $params = array(1 => array($id, 'Integer')); - $dao = CRM_Core_DAO::executeQuery($sql, $params); - if ($dao->fetch()) { - return array($dao->display_name, $dao->email, $dao->location_type_id, $dao->id); - } - return array(NULL, NULL, NULL, NULL); + $returnParams = array( + (isset($contact['display_name'])) ? $contact['display_name'] : NULL, + (isset($email['email'])) ? $email['email'] : NULL, + (isset($email['location_type_id'])) ? $email['location_type_id'] : NULL, + (isset($email['id'])) ? $email['id'] : NULL, + ); + + return $returnParams; } /** + * @deprecated Not used anywhere, use the Phone API instead * Get the sms number and display name of a contact. * * @param int $id @@ -84,8 +88,9 @@ public static function getEmailDetails($id, $isPrimary = TRUE, $locationTypeID = * tuple of display_name and sms if found, or (null,null) */ public static function getPhoneDetails($id, $type = NULL) { + CRM_Core_Error::deprecatedFunctionWarning('Phone.get API instead'); if (!$id) { - return array(NULL, NULL); + return [NULL, NULL]; } $cond = NULL; @@ -101,12 +106,12 @@ public static function getPhoneDetails($id, $type = NULL) { $cond AND civicrm_contact.id = %1"; - $params = array(1 => array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); if ($dao->fetch()) { - return array($dao->display_name, $dao->phone, $dao->do_not_sms); + return [$dao->display_name, $dao->phone, $dao->do_not_sms]; } - return array(NULL, NULL, NULL); + return [NULL, NULL, NULL]; } /** @@ -150,22 +155,22 @@ public static function &getMapInfo($ids, $locationTypeID = NULL, $imageUrlOnly = AND civicrm_address.geo_code_2 IS NOT NULL AND civicrm_contact.id IN $idString "; - $params = array(); + $params = []; if (!$locationTypeID) { $sql .= " AND civicrm_address.is_primary = 1"; } else { $sql .= " AND civicrm_address.location_type_id = %1"; - $params[1] = array($locationTypeID, 'Integer'); + $params[1] = [$locationTypeID, 'Integer']; } $dao = CRM_Core_DAO::executeQuery($sql, $params); - $locations = array(); + $locations = []; $config = CRM_Core_Config::singleton(); while ($dao->fetch()) { - $location = array(); + $location = []; $location['contactID'] = $dao->contact_id; $location['displayName'] = addslashes($dao->display_name); $location['city'] = $dao->city; @@ -177,19 +182,19 @@ public static function &getMapInfo($ids, $locationTypeID = NULL, $imageUrlOnly = $address = ''; CRM_Utils_String::append($address, '
    ', - array( + [ $dao->street_address, $dao->supplemental_address_1, $dao->supplemental_address_2, $dao->supplemental_address_3, $dao->city, - ) + ] ); CRM_Utils_String::append($address, ', ', - array($dao->state, $dao->postal_code) + [$dao->state, $dao->postal_code] ); CRM_Utils_String::append($address, '
    ', - array($dao->country) + [$dao->country] ); $location['address'] = addslashes($address); $location['displayAddress'] = str_replace('
    ', ', ', addslashes($address)); diff --git a/CRM/Contact/BAO/Contact/Optimizer.php b/CRM/Contact/BAO/Contact/Optimizer.php deleted file mode 100644 index c9766ccd8740..000000000000 --- a/CRM/Contact/BAO/Contact/Optimizer.php +++ /dev/null @@ -1,190 +0,0 @@ - $value) { - if (!empty($value['url'])) { - $oldEmpty = FALSE; - $old[] = array('website_type_id' => $value['website_type_id'], 'url' => $value['url']); - } - } - - foreach ($newWebsiteValues as $idx => $value) { - if (!empty($value['url'])) { - $newEmpty = FALSE; - $new[] = array('website_type_id' => $value['website_type_id'], 'url' => $value['url']); - } - } - - // if both old and new are empty, we can delete new and avoid a write - if ($oldEmpty && $newEmpty) { - unset($newValues['website']); - } - - // if different number of non-empty entries, return - if (count($new) != count($old)) { - return; - } - - // same number of entries, check if they are exactly the same - foreach ($old as $oldID => $oldValues) { - $found = FALSE; - foreach ($new as $newID => $newValues) { - if ( - $old['website_type_id'] == $new['website_type_id'] && - $old['url'] == $new['url'] - ) { - $found = TRUE; - unset($new[$newID]); - break; - } - if (!$found) { - return; - } - } - } - - // if we've come here, this means old and new are the same - // we can skip saving new and return - unset($newValues['website']); - } - - /** - * @param $newValues - * @param $oldValues - */ - public static function email(&$newValues, &$oldValues) { - $oldEmailValues = CRM_Utils_Array::value('email', $oldValues); - $newEmailValues = CRM_Utils_Array::value('email', $newValues); - - if ($oldEmailValues == NULL || $newEmailValues == NULL) { - return; - } - - // check if we had a value in the old - $oldEmpty = $newEmpty = TRUE; - $old = $new = array(); - - foreach ($oldEmailValues as $idx => $value) { - if (!empty($value['email'])) { - $oldEmpty = FALSE; - $old[] = array( - 'email' => $value['email'], - 'location_type_id' => $value['location_type_id'], - 'on_hold' => $value['on_hold'] ? 1 : 0, - 'is_primary' => $value['is_primary'] ? 1 : 0, - 'is_bulkmail' => $value['is_bulkmail'] ? 1 : 0, - 'signature_text' => $value['signature_text'] ? $value['signature_text'] : '', - 'signature_html' => $value['signature_html'] ? $value['signature_html'] : '', - ); - } - } - - foreach ($newEmailValues as $idx => $value) { - if (!empty($value['email'])) { - $newEmpty = FALSE; - $new[] = array( - 'email' => $value['email'], - 'location_type_id' => $value['location_type_id'], - 'on_hold' => $value['on_hold'] ? 1 : 0, - 'is_primary' => $value['is_primary'] ? 1 : 0, - 'is_bulkmail' => $value['is_bulkmail'] ? 1 : 0, - 'signature_text' => $value['signature_text'] ? $value['signature_text'] : '', - 'signature_html' => $value['signature_html'] ? $value['signature_html'] : '', - ); - } - } - - // if both old and new are empty, we can delete new and avoid a write - if ($oldEmpty && $newEmpty) { - unset($newValues['email']); - } - - // if different number of non-empty entries, return - if (count($new) != count($old)) { - return; - } - - // same number of entries, check if they are exactly the same - foreach ($old as $oldID => $oldValues) { - $found = FALSE; - foreach ($new as $newID => $newValues) { - if ( - $old['email_type_id'] == $new['email_type_id'] && - $old['url'] == $new['url'] - ) { - $found = TRUE; - unset($new[$newID]); - break; - } - if (!$found) { - return; - } - } - } - - // if we've come here, this means old and new are the same - // we can skip saving new and return - unset($newValues['email']); - } - -} diff --git a/CRM/Contact/BAO/Contact/Permission.php b/CRM/Contact/BAO/Contact/Permission.php index 73c379e0dfff..80cbe37847b0 100644 --- a/CRM/Contact/BAO/Contact/Permission.php +++ b/CRM/Contact/BAO/Contact/Permission.php @@ -1,9 +1,9 @@ array($id, 'Integer')))) { + if (CRM_Core_DAO::singleValueQuery($query, [1 => [$id, 'Integer']])) { return TRUE; } return FALSE; @@ -199,9 +199,12 @@ public static function cache($userID, $type = CRM_Core_Permission::VIEW, $force // that somebody might flush the cache away from under our feet, // but the alternative would be a SQL call every time this is called, // and a complete rebuild if the result was an empty set... - static $_processed = array( - CRM_Core_Permission::VIEW => array(), - CRM_Core_Permission::EDIT => array()); + if (!isset(Civi::$statics[__CLASS__]['processed'])) { + Civi::$statics[__CLASS__]['processed'] = [ + CRM_Core_Permission::VIEW => [], + CRM_Core_Permission::EDIT => [], + ]; + } if ($type == CRM_Core_Permission::VIEW) { $operationClause = " operation IN ( 'Edit', 'View' ) "; @@ -211,11 +214,11 @@ public static function cache($userID, $type = CRM_Core_Permission::VIEW, $force $operationClause = " operation = 'Edit' "; $operation = 'Edit'; } - $queryParams = array(1 => array($userID, 'Integer')); + $queryParams = [1 => [$userID, 'Integer']]; if (!$force) { // skip if already calculated - if (!empty($_processed[$type][$userID])) { + if (!empty(Civi::$statics[__CLASS__]['processed'][$type][$userID])) { return; } @@ -228,13 +231,13 @@ public static function cache($userID, $type = CRM_Core_Permission::VIEW, $force "; $count = CRM_Core_DAO::singleValueQuery($sql, $queryParams); if ($count > 0) { - $_processed[$type][$userID] = 1; + Civi::$statics[__CLASS__]['processed'][$type][$userID] = 1; return; } } - $tables = array(); - $whereTables = array(); + $tables = []; + $whereTables = []; $permission = CRM_ACL_API::whereClause($type, $tables, $whereTables, $userID, FALSE, FALSE, TRUE); @@ -257,7 +260,7 @@ public static function cache($userID, $type = CRM_Core_Permission::VIEW, $force CRM_Core_DAO::executeQuery("INSERT INTO civicrm_acl_contact_cache ( user_id, contact_id, operation ) VALUES(%1, %1, '{$operation}')", $queryParams); } } - $_processed[$type][$userID] = 1; + Civi::$statics[__CLASS__]['processed'][$type][$userID] = 1; } /** @@ -270,16 +273,16 @@ public static function cacheClause($contactAlias = 'contact_a') { CRM_Core_Permission::check('edit all contacts') ) { if (is_array($contactAlias)) { - $wheres = array(); + $wheres = []; foreach ($contactAlias as $alias) { // CRM-6181 $wheres[] = "$alias.is_deleted = 0"; } - return array(NULL, '(' . implode(' AND ', $wheres) . ')'); + return [NULL, '(' . implode(' AND ', $wheres) . ')']; } else { // CRM-6181 - return array(NULL, "$contactAlias.is_deleted = 0"); + return [NULL, "$contactAlias.is_deleted = 0"]; } } @@ -288,7 +291,7 @@ public static function cacheClause($contactAlias = 'contact_a') { if (is_array($contactAlias) && !empty($contactAlias)) { //More than one contact alias - $clauses = array(); + $clauses = []; foreach ($contactAlias as $k => $alias) { $clauses[] = " INNER JOIN civicrm_acl_contact_cache aclContactCache_{$k} ON {$alias}.id = aclContactCache_{$k}.contact_id AND aclContactCache_{$k}.user_id = $contactID "; } @@ -301,7 +304,7 @@ public static function cacheClause($contactAlias = 'contact_a') { $whereClase = " aclContactCache.user_id = $contactID AND $contactAlias.is_deleted = 0"; } - return array($fromClause, $whereClase); + return [$fromClause, $whereClase]; } /** @@ -312,7 +315,7 @@ public static function cacheClause($contactAlias = 'contact_a') { * @return string|null */ public static function cacheSubquery() { - if (!CRM_Core_Permission::check(array(array('view all contacts', 'edit all contacts')))) { + if (!CRM_Core_Permission::check([['view all contacts', 'edit all contacts']])) { $contactID = (int) CRM_Core_Session::getLoggedInContactID(); self::cache($contactID); return "IN (SELECT contact_id FROM civicrm_acl_contact_cache WHERE user_id = $contactID)"; @@ -327,29 +330,40 @@ public static function cacheSubquery() { * @param array $contact_ids * List of contact IDs to be filtered * + * @param int $type + * access type CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT + * * @return array * List of contact IDs that the user has permissions for */ - public static function relationshipList($contact_ids) { - $result_set = array(); + public static function relationshipList($contact_ids, $type) { + $result_set = []; // no processing empty lists (avoid SQL errors as well) if (empty($contact_ids)) { - return array(); + return []; } // get the currently logged in user $contactID = CRM_Core_Session::getLoggedInContactID(); if (empty($contactID)) { - return array(); + return []; } // compile a list of queries (later to UNION) - $queries = array(); + $queries = []; $contact_id_list = implode(',', $contact_ids); - // add a select statement for each direection - $directions = array(array('from' => 'a', 'to' => 'b'), array('from' => 'b', 'to' => 'a')); + // add a select statement for each direction + $directions = [['from' => 'a', 'to' => 'b'], ['from' => 'b', 'to' => 'a']]; + + // CRM_Core_Permission::VIEW is satisfied by either CRM_Contact_BAO_Relationship::VIEW or CRM_Contact_BAO_Relationship::EDIT + if ($type == CRM_Core_Permission::VIEW) { + $is_perm_condition = ' IN ( ' . CRM_Contact_BAO_Relationship::EDIT . ' , ' . CRM_Contact_BAO_Relationship::VIEW . ' ) '; + } + else { + $is_perm_condition = ' = ' . CRM_Contact_BAO_Relationship::EDIT; + } // NORMAL/SINGLE DEGREE RELATIONSHIPS foreach ($directions as $direction) { @@ -370,7 +384,7 @@ public static function relationshipList($contact_ids) { WHERE civicrm_relationship.{$user_id_column} = {$contactID} AND civicrm_relationship.{$contact_id_column} IN ({$contact_id_list}) AND civicrm_relationship.is_active = 1 - AND civicrm_relationship.is_permission_{$direction['from']}_{$direction['to']} = 1 + AND civicrm_relationship.is_permission_{$direction['from']}_{$direction['to']} {$is_perm_condition} $AND_CAN_ACCESS_DELETED"; } @@ -391,14 +405,14 @@ public static function relationshipList($contact_ids) { $queries[] = " SELECT second_degree_relationship.contact_id_{$second_direction['to']} AS contact_id FROM civicrm_relationship first_degree_relationship - LEFT JOIN civicrm_relationship second_degree_relationship ON first_degree_relationship.contact_id_{$first_direction['to']} = second_degree_relationship.contact_id_{$first_direction['from']} + LEFT JOIN civicrm_relationship second_degree_relationship ON first_degree_relationship.contact_id_{$first_direction['to']} = second_degree_relationship.contact_id_{$second_direction['from']} {$LEFT_JOIN_DELETED} WHERE first_degree_relationship.contact_id_{$first_direction['from']} = {$contactID} AND second_degree_relationship.contact_id_{$second_direction['to']} IN ({$contact_id_list}) AND first_degree_relationship.is_active = 1 - AND first_degree_relationship.is_permission_{$first_direction['from']}_{$first_direction['to']} = 1 + AND first_degree_relationship.is_permission_{$first_direction['from']}_{$first_direction['to']} {$is_perm_condition} AND second_degree_relationship.is_active = 1 - AND second_degree_relationship.is_permission_{$second_direction['from']}_{$second_direction['to']} = 1 + AND second_degree_relationship.is_permission_{$second_direction['from']}_{$second_direction['to']} {$is_perm_condition} $AND_CAN_ACCESS_DELETED"; } } @@ -413,7 +427,6 @@ public static function relationshipList($contact_ids) { return array_keys($result_set); } - /** * @param int $contactID * @param CRM_Core_Form $form @@ -447,7 +460,7 @@ public static function validateOnlyChecksum($contactID, &$form, $redirect = TRUE // so here the contact is posing as $contactID, lets set the logging contact ID variable // CRM-8965 CRM_Core_DAO::executeQuery('SET @civicrm_user_id = %1', - array(1 => array($contactID, 'Integer')) + [1 => [$contactID, 'Integer']] ); return TRUE; diff --git a/CRM/Contact/BAO/Contact/Utils.php b/CRM/Contact/BAO/Contact/Utils.php index 9cbb44d53b0c..28f380249093 100644 --- a/CRM/Contact/BAO/Contact/Utils.php +++ b/CRM/Contact/BAO/Contact/Utils.php @@ -1,9 +1,9 @@ $contactType); + $typeInfo = []; + $params = ['name' => $contactType]; CRM_Contact_BAO_ContactType::retrieve($params, $typeInfo); if (!empty($typeInfo['image_URL'])) { @@ -132,9 +132,7 @@ public static function checkContactType(&$contactIds) { FROM civicrm_contact WHERE id IN ( $idString ) "; - $count = CRM_Core_DAO::singleValueQuery($query, - CRM_Core_DAO::$_nullArray - ); + $count = CRM_Core_DAO::singleValueQuery($query); return $count > 1 ? TRUE : FALSE; } @@ -228,8 +226,9 @@ public static function validChecksum($contactID, $inputCheck) { $inputLF = CRM_Utils_Array::value(2, $input); $check = self::generateChecksum($contactID, $inputTS, $inputLF); - - if ($check != $inputCheck) { + // Joomla_11 - If $inputcheck is null without explicitly casting to a string + // you get an error. + if (!hash_equals($check, (string) $inputCheck)) { return FALSE; } @@ -243,33 +242,6 @@ public static function validChecksum($contactID, $inputCheck) { return ($inputTS + ($inputLF * 60 * 60) >= $now); } - /** - * Get the count of contact loctions. - * - * @param int $contactId - * Contact id. - * - * @return int - * max locations for the contact - */ - public static function maxLocations($contactId) { - $contactLocations = array(); - - // find number of location blocks for this contact and adjust value accordinly - // get location type from email - $query = " -( SELECT location_type_id FROM civicrm_email WHERE contact_id = {$contactId} ) -UNION -( SELECT location_type_id FROM civicrm_phone WHERE contact_id = {$contactId} ) -UNION -( SELECT location_type_id FROM civicrm_im WHERE contact_id = {$contactId} ) -UNION -( SELECT location_type_id FROM civicrm_address WHERE contact_id = {$contactId} ) -"; - $dao = CRM_Core_DAO::executeQuery($query); - return $dao->N; - } - /** * Create Current employer relationship for a individual. * @@ -284,7 +256,7 @@ public static function maxLocations($contactId) { public static function createCurrentEmployerRelationship($contactID, $organization, $previousEmployerID = NULL, $newContact = FALSE) { //if organization name is passed. CRM-15368,CRM-15547 if ($organization && !is_numeric($organization)) { - $dupeIDs = CRM_Contact_BAO_Contact::getDuplicateContacts(array('organization_name' => $organization), 'Organization', 'Unsupervised', array(), FALSE); + $dupeIDs = CRM_Contact_BAO_Contact::getDuplicateContacts(['organization_name' => $organization], 'Organization', 'Unsupervised', [], FALSE); if (is_array($dupeIDs) && !empty($dupeIDs)) { // we should create relationship only w/ first org CRM-4193 @@ -295,17 +267,17 @@ public static function createCurrentEmployerRelationship($contactID, $organizati } else { //create new organization - $newOrg = array( + $newOrg = [ 'contact_type' => 'Organization', 'organization_name' => $organization, - ); + ]; $org = CRM_Contact_BAO_Contact::create($newOrg); $organization = $org->id; } } if ($organization && is_numeric($organization)) { - $cid = array('contact' => $contactID); + $cid = ['contact' => $contactID]; // get the relationship type id of "Employee of" $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b'); @@ -314,11 +286,11 @@ public static function createCurrentEmployerRelationship($contactID, $organizati } // create employee of relationship - $relationshipParams = array( + $relationshipParams = [ 'is_active' => TRUE, 'relationship_type_id' => $relTypeId . '_a_b', - 'contact_check' => array($organization => TRUE), - ); + 'contact_check' => [$organization => TRUE], + ]; list($valid, $invalid, $duplicate, $saved, $relationshipIds) = CRM_Contact_BAO_Relationship::legacyCreateMultiple($relationshipParams, $cid); @@ -333,7 +305,7 @@ public static function createCurrentEmployerRelationship($contactID, $organizati } // set current employer - self::setCurrentEmployer(array($contactID => $organization)); + self::setCurrentEmployer([$contactID => $organization]); $relationshipParams['relationship_ids'] = $relationshipIds; // Handle related memberships. CRM-3792 @@ -358,7 +330,7 @@ public static function createCurrentEmployerRelationship($contactID, $organizati * @throws CiviCRM_API3_Exception */ public static function currentEmployerRelatedMembership($contactID, $employerID, $relationshipParams, $duplicate = FALSE, $previousEmpID = NULL) { - $ids = array(); + $ids = []; $action = CRM_Core_Action::ADD; //we do not know that triggered relationship record is active. @@ -374,7 +346,6 @@ public static function currentEmployerRelatedMembership($contactID, $employerID, $ids['relationship'] = $relationship->id; CRM_Contact_BAO_Relationship::setIsActive($relationship->id, TRUE); } - $relationship->free(); } //need to handle related meberships. CRM-3792 @@ -394,10 +365,7 @@ public static function setCurrentEmployer($currentEmployerParams) { $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b SET contact_a.employer_id=contact_b.id, contact_a.organization_name=contact_b.organization_name WHERE contact_a.id ={$contactId} AND contact_b.id={$orgId}; "; - - //FIXME : currently civicrm mysql_query support only single statement - //execution, though mysql 5.0 support multiple statement execution. - $dao = CRM_Core_DAO::executeQuery($query); + CRM_Core_DAO::executeQuery($query); } } @@ -453,11 +421,10 @@ public static function clearCurrentEmployer($contactId, $employerId = NULL) { if ($relationship->find(TRUE)) { CRM_Contact_BAO_Relationship::setIsActive($relationship->id, FALSE); CRM_Contact_BAO_Relationship::relatedMemberships($contactId, $relMembershipParams, - $ids = array(), + $ids = [], CRM_Core_Action::DELETE ); } - $relationship->free(); } } } @@ -475,9 +442,6 @@ public static function clearCurrentEmployer($contactId, $employerId = NULL) { * */ public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stateID, $title) { - - $config = CRM_Core_Config::singleton(); - $form->assign('contact_type', $contactType); $form->assign('fieldSetTitle', $title); $form->assign('contactEditMode', TRUE); @@ -499,7 +463,7 @@ public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stat default: // individual $form->addElement('select', 'prefix_id', ts('Prefix'), - array('' => ts('- prefix -')) + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id') + ['' => ts('- prefix -')] + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id') ); $form->addElement('text', 'first_name', ts('First Name'), $attributes['first_name'] @@ -511,11 +475,11 @@ public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stat $attributes['last_name'] ); $form->addElement('select', 'suffix_id', ts('Suffix'), - array('' => ts('- suffix -')) + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id') + ['' => ts('- suffix -')] + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id') ); } - $addressSequence = $config->addressSequence(); + $addressSequence = CRM_Utils_Address::sequence(\Civi::settings()->get('address_format')); $form->assign('addressSequence', array_fill_keys($addressSequence, 1)); //Primary Phone @@ -569,19 +533,19 @@ public static function clearAllEmployee($employerId) { * returns array with links to contact view */ public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, $addEditLink = TRUE, $originalId = NULL) { - $contactLinks = array(); + $contactLinks = []; if (!is_array($contactIDs) || empty($contactIDs)) { return $contactLinks; } // does contact has sufficient permissions. - $permissions = array( + $permissions = [ 'view' => 'view all contacts', 'edit' => 'edit all contacts', 'merge' => 'merge duplicate contacts', - ); + ]; - $permissionedContactIds = array(); + $permissionedContactIds = []; foreach ($permissions as $task => $permission) { // give permission. if (CRM_Core_Permission::check($permission)) { @@ -592,10 +556,10 @@ public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, } // check permission on acl basis. - if (in_array($task, array( + if (in_array($task, [ 'view', 'edit', - ))) { + ])) { $aclPermission = CRM_Core_Permission::VIEW; if ($task == 'edit') { $aclPermission = CRM_Core_Permission::EDIT; @@ -676,10 +640,10 @@ public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, * @return array * array of contact info. */ - public static function contactDetails($componentIds, $componentName, $returnProperties = array()) { - $contactDetails = array(); + public static function contactDetails($componentIds, $componentName, $returnProperties = []) { + $contactDetails = []; if (empty($componentIds) || - !in_array($componentName, array('CiviContribute', 'CiviMember', 'CiviEvent', 'Activity')) + !in_array($componentName, ['CiviContribute', 'CiviMember', 'CiviEvent', 'Activity', 'CiviCase']) ) { return $contactDetails; } @@ -688,7 +652,7 @@ public static function contactDetails($componentIds, $componentName, $returnProp $autocompleteContactSearch = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_autocomplete_options' ); - $returnProperties = array_fill_keys(array_merge(array('sort_name'), + $returnProperties = array_fill_keys(array_merge(['sort_name'], array_keys($autocompleteContactSearch) ), 1); } @@ -702,19 +666,22 @@ public static function contactDetails($componentIds, $componentName, $returnProp } elseif ($componentName == 'Activity') { $compTable = 'civicrm_activity'; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); + } + elseif ($componentName == 'CiviCase') { + $compTable = 'civicrm_case'; } else { $compTable = 'civicrm_participant'; } - $select = $from = array(); + $select = $from = []; foreach ($returnProperties as $property => $ignore) { - $value = (in_array($property, array( + $value = (in_array($property, [ 'city', 'street_address', 'postal_code', - ))) ? 'address' : $property; + ])) ? 'address' : $property; switch ($property) { case 'sort_name': if ($componentName == 'Activity') { @@ -723,6 +690,12 @@ public static function contactDetails($componentIds, $componentName, $returnProp $from[$value] = " INNER JOIN civicrm_activity_contact acs ON (acs.activity_id = {$compTable}.id AND acs.record_type_id = {$sourceID}) INNER JOIN civicrm_contact contact ON ( contact.id = acs.contact_id )"; + } + elseif ($componentName == 'CiviCase') { + $select[] = "contact.$property as $property"; + $from[$value] = " +INNER JOIN civicrm_case_contact ccs ON (ccs.case_id = {$compTable}.id) +INNER JOIN civicrm_contact contact ON ( contact.id = ccs.contact_id )"; } else { $select[] = "$property as $property"; @@ -775,7 +748,7 @@ public static function contactDetails($componentIds, $componentName, $returnProp $fromClause = implode(' ', $from); $selectClause = implode(', ', $select); $whereClause = "{$compTable}.id IN (" . implode(',', $componentIds) . ')'; - $groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($select, array("{$compTable}.id", 'contact.id')); + $groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($select, ["{$compTable}.id", 'contact.id']); $query = " SELECT contact.id as contactId, $compTable.id as componentId, $selectClause @@ -790,7 +763,6 @@ public static function contactDetails($componentIds, $componentName, $returnProp $contactDetails[$contact->componentId][$property] = $contact->$property; } } - $contact->free(); } return $contactDetails; @@ -800,7 +772,7 @@ public static function contactDetails($componentIds, $componentName, $returnProp * Function handles shared contact address processing. * In this function we just modify submitted values so that new address created for the user * has same address as shared contact address. We copy the address so that search etc will be - * much efficient. + * much more efficient. * * @param array $address * This is associated array which contains submitted form values. @@ -810,23 +782,26 @@ public static function processSharedAddress(&$address) { return; } - // Sharing contact address during create mode is pretty straight forward. - // In update mode we should check following: - // - We should check if user has uncheck shared contact address - // - If yes then unset the master_id or may be just delete the address that copied master - // Normal update process will automatically create new address with submitted values + // In create mode sharing a contact's address is pretty straight forward. + // In update mode we should check if the user stops sharing. If yes: + // - Set the master_id to an empty value + // Normal update process will automatically create new address with submitted values - // 1. loop through entire subnitted address array - $masterAddress = array(); - $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id'); + // 1. loop through entire submitted address array + $skipFields = ['is_primary', 'location_type_id', 'is_billing', 'master_id', 'update_current_employer']; foreach ($address as & $values) { - // 2. check if master id exists, if not continue - if (empty($values['master_id']) || empty($values['use_shared_address'])) { - // we should unset master id when use uncheck share address for existing address - $values['master_id'] = 'null'; + // 2. check if "Use another contact's address" is checked, if not continue + // Additionally, if master_id is set (address was shared), set master_id to empty value. + if (empty($values['use_shared_address'])) { + if (!empty($values['master_id'])) { + $values['master_id'] = ''; + } continue; } + // Set update_current_employer checkbox value + $values['update_current_employer'] = !empty($values['update_current_employer']); + // 3. get the address details for master_id $masterAddress = new CRM_Core_BAO_Address(); $masterAddress->id = CRM_Utils_Array::value('master_id', $values); @@ -864,9 +839,9 @@ public static function processSharedAddress(&$address) { * associated array of contact names */ public static function getAddressShareContactNames(&$addresses) { - $contactNames = array(); + $contactNames = []; // get the list of master id's for address - $masterAddressIds = array(); + $masterAddressIds = []; foreach ($addresses as $key => $addressValue) { if (!empty($addressValue['master_id'])) { $masterAddressIds[] = $addressValue['master_id']; @@ -882,11 +857,11 @@ public static function getAddressShareContactNames(&$addresses) { while ($dao->fetch()) { $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$dao->cid}"); - $contactNames[$dao->id] = array( + $contactNames[$dao->id] = [ 'name' => "{$dao->display_name}", 'is_deleted' => $dao->is_deleted, 'contact_id' => $dao->cid, - ); + ]; } } return $contactNames; @@ -897,18 +872,24 @@ public static function getAddressShareContactNames(&$addresses) { * caches, but are backing off from this with every release. Compromise between ease of coding versus * performance versus being accurate at that very instant * - * @param $contactID - * The contactID that was edited / deleted. + * @param bool $isEmptyPrevNextTable + * Should the civicrm_prev_next table be cleared of any contact entries. + * This is currently done from import but not other places and would + * likely affect user experience in unexpected ways. Existing behaviour retained + * ... reluctantly. */ - public static function clearContactCaches($contactID = NULL) { - // clear acl cache if any. - CRM_ACL_BAO_Cache::resetCache(); - - if (empty($contactID)) { - // also clear prev/next dedupe cache - if no contactID passed in + public static function clearContactCaches($isEmptyPrevNextTable = FALSE) { + if (!CRM_Core_Config::isPermitCacheFlushMode()) { + return; + } + if ($isEmptyPrevNextTable) { + // These two calls are redundant in default deployments, but they're + // meaningful if "prevnext" is memory-backed. + Civi::service('prevnext')->deleteItem(); CRM_Core_BAO_PrevNextCache::deleteItem(); } - + // clear acl cache if any. + CRM_ACL_BAO_Cache::resetCache(); CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); } @@ -929,21 +910,21 @@ public static function updateGreeting($params) { $valueID = $id = self::defaultGreeting($contactType, $greeting); } - $filter = array( + $filter = [ 'contact_type' => $contactType, 'greeting_type' => $greeting, - ); + ]; $allGreetings = CRM_Core_PseudoConstant::greeting($filter); $originalGreetingString = $greetingString = CRM_Utils_Array::value($valueID, $allGreetings); if (!$greetingString) { - CRM_Core_Error::fatal(ts('Incorrect greeting value id %1, or no default greeting for this contact type and greeting type.', array(1 => $valueID))); + CRM_Core_Error::fatal(ts('Incorrect greeting value id %1, or no default greeting for this contact type and greeting type.', [1 => $valueID])); } // build return properties based on tokens $greetingTokens = CRM_Utils_Token::getTokens($greetingString); $tokens = CRM_Utils_Array::value('contact', $greetingTokens); - $greetingsReturnProperties = array(); + $greetingsReturnProperties = []; if (is_array($tokens)) { $greetingsReturnProperties = array_fill_keys(array_values($tokens), 1); } @@ -958,7 +939,7 @@ public static function updateGreeting($params) { } //FIXME : apiQuery should handle these clause. - $filterContactFldIds = $filterIds = array(); + $filterContactFldIds = $filterIds = []; $idFldName = $displayFldName = NULL; if (in_array($greeting, CRM_Contact_BAO_Contact::$_greetingTypes)) { $idFldName = $greeting . '_id'; @@ -966,7 +947,7 @@ public static function updateGreeting($params) { } if ($idFldName) { - $queryParams = array(1 => array($contactType, 'String')); + $queryParams = [1 => [$contactType, 'String']]; // if $force == 1 then update all contacts else only // those with NULL greeting or addressee value CRM-9476 @@ -984,7 +965,7 @@ public static function updateGreeting($params) { if ($limit) { $sql .= " LIMIT 0, %2"; - $queryParams += array(2 => array($limit, 'Integer')); + $queryParams += [2 => [$limit, 'Integer']]; } $dao = CRM_Core_DAO::executeQuery($sql, $queryParams); @@ -1002,14 +983,14 @@ public static function updateGreeting($params) { } // retrieve only required contact information - $extraParams[] = array('contact_type', '=', $contactType, 0, 0); + $extraParams[] = ['contact_type', '=', $contactType, 0, 0]; // we do token replacement in the replaceGreetingTokens hook list($greetingDetails) = CRM_Utils_Token::getTokenDetails(array_keys($filterContactFldIds), $greetingsReturnProperties, FALSE, FALSE, $extraParams ); // perform token replacement and build update SQL - $contactIds = array(); + $contactIds = []; $cacheFieldQuery = "UPDATE civicrm_contact SET {$greeting}_display = CASE id "; foreach ($greetingDetails as $contactID => $contactDetails) { if (!$processAll && @@ -1071,14 +1052,14 @@ public static function updateGreeting($params) { * @param string $greetingType * Greeting type. * - * @return int|NULL + * @return int|null */ public static function defaultGreeting($contactType, $greetingType) { - $contactTypeFilters = array( + $contactTypeFilters = [ 'Individual' => 1, 'Household' => 2, 'Organization' => 3, - ); + ]; if (!isset($contactTypeFilters[$contactType])) { return NULL; } @@ -1093,6 +1074,34 @@ public static function defaultGreeting($contactType, $greetingType) { } } + /** + * Get the tokens that will need to be resolved to populate the contact's greetings. + * + * @param array $contactParams + * + * @return array + * Array of tokens. The ALL ke + */ + public static function getTokensRequiredForContactGreetings($contactParams) { + $tokens = []; + foreach (['addressee', 'email_greeting', 'postal_greeting'] as $greeting) { + $string = ''; + if (!empty($contactParams[$greeting . '_id'])) { + $string = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $greeting . '_id', $contactParams[$greeting . '_id']); + } + $string = isset($contactParams[$greeting . '_custom']) ? $contactParams[$greeting . '_custom'] : $string; + if (empty($string)) { + $tokens[$greeting] = []; + } + else { + $tokens[$greeting] = CRM_Utils_Token::getTokens($string); + } + } + $allTokens = array_merge_recursive($tokens['addressee'], $tokens['email_greeting'], $tokens['postal_greeting']); + $tokens['all'] = $allTokens; + return $tokens; + } + /** * Process a greeting template string to produce the individualised greeting text. * diff --git a/CRM/Contact/BAO/ContactType.php b/CRM/Contact/BAO/ContactType.php index 2efb3774647a..a4d7cc6af43f 100644 --- a/CRM/Contact/BAO/ContactType.php +++ b/CRM/Contact/BAO/ContactType.php @@ -1,9 +1,9 @@ fetch()) { - $value = array(); + $value = []; CRM_Core_DAO::storeValues($dao, $value); $_cache[$argString][$dao->name] = $value; } @@ -134,7 +134,7 @@ public static function basicTypes($all = FALSE) { public static function basicTypePairs($all = FALSE, $key = 'name') { $subtypes = self::basicTypeInfo($all); - $pairs = array(); + $pairs = []; foreach ($subtypes as $name => $info) { $index = ($key == 'name') ? $name : $info[$key]; $pairs[$index] = $info['label']; @@ -149,65 +149,45 @@ public static function basicTypePairs($all = FALSE, $key = 'name') { * .. * @param bool $all * @param bool $ignoreCache - * @param bool $reset * * @return array * Array of sub type information */ - public static function subTypeInfo($contactType = NULL, $all = FALSE, $ignoreCache = FALSE, $reset = FALSE) { - static $_cache = NULL; - - if ($reset === TRUE) { - $_cache = NULL; - } - - if ($_cache === NULL) { - $_cache = array(); - } - if ($contactType && !is_array($contactType)) { - $contactType = array($contactType); - } - + public static function subTypeInfo($contactType = NULL, $all = FALSE, $ignoreCache = FALSE) { $argString = $all ? 'CRM_CT_STI_1_' : 'CRM_CT_STI_0_'; if (!empty($contactType)) { + $contactType = (array) $contactType; $argString .= implode('_', $contactType); } + if (!Civi::cache('contactTypes')->has($argString) || $ignoreCache) { + $ctWHERE = ''; + if (!empty($contactType)) { + $ctWHERE = " AND parent.name IN ('" . implode("','", $contactType) . "')"; + } - if ((!array_key_exists($argString, $_cache)) || $ignoreCache) { - $cache = CRM_Utils_Cache::singleton(); - $_cache[$argString] = $cache->get($argString); - if (!$_cache[$argString] || $ignoreCache) { - $_cache[$argString] = array(); - - $ctWHERE = ''; - if (!empty($contactType)) { - $ctWHERE = " AND parent.name IN ('" . implode("','", $contactType) . "')"; - } - - $sql = " + $sql = " SELECT subtype.*, parent.name as parent, parent.label as parent_label FROM civicrm_contact_type subtype INNER JOIN civicrm_contact_type parent ON subtype.parent_id = parent.id WHERE subtype.name IS NOT NULL AND subtype.parent_id IS NOT NULL {$ctWHERE} "; - if ($all === FALSE) { - $sql .= " AND subtype.is_active = 1 AND parent.is_active = 1 ORDER BY parent.id"; - } - $dao = CRM_Core_DAO::executeQuery($sql, array(), - FALSE, 'CRM_Contact_DAO_ContactType' - ); - while ($dao->fetch()) { - $value = array(); - CRM_Core_DAO::storeValues($dao, $value); - $value['parent'] = $dao->parent; - $value['parent_label'] = $dao->parent_label; - $_cache[$argString][$dao->name] = $value; - } - - $cache->set($argString, $_cache[$argString]); + if ($all === FALSE) { + $sql .= " AND subtype.is_active = 1 AND parent.is_active = 1 ORDER BY parent.id"; } + $dao = CRM_Core_DAO::executeQuery($sql, [], + FALSE, 'CRM_Contact_DAO_ContactType' + ); + $values = []; + while ($dao->fetch()) { + $value = []; + CRM_Core_DAO::storeValues($dao, $value); + $value['parent'] = $dao->parent; + $value['parent_label'] = $dao->parent_label; + $values[$dao->name] = $value; + } + Civi::cache('contactTypes')->set($argString, $values); } - return $_cache[$argString]; + return Civi::cache('contactTypes')->get($argString); } /** @@ -248,7 +228,7 @@ public static function subTypes($contactType = NULL, $all = FALSE, $columnName = public static function subTypePairs($contactType = NULL, $all = FALSE, $labelPrefix = '- ', $ignoreCache = FALSE) { $subtypes = self::subTypeInfo($contactType, $all, $ignoreCache); - $pairs = array(); + $pairs = []; foreach ($subtypes as $name => $info) { $pairs[$name] = $labelPrefix . $info['label']; } @@ -285,7 +265,7 @@ public static function contactTypeInfo($all = FALSE, $reset = FALSE) { } if ($_cache === NULL) { - $_cache = array(); + $_cache = []; } $argString = $all ? 'CRM_CT_CTI_1' : 'CRM_CT_CTI_0'; @@ -293,7 +273,7 @@ public static function contactTypeInfo($all = FALSE, $reset = FALSE) { $cache = CRM_Utils_Cache::singleton(); $_cache[$argString] = $cache->get($argString); if (!$_cache[$argString]) { - $_cache[$argString] = array(); + $_cache[$argString] = []; $sql = " SELECT type.*, parent.name as parent, parent.label as parent_label @@ -306,12 +286,12 @@ public static function contactTypeInfo($all = FALSE, $reset = FALSE) { } $dao = CRM_Core_DAO::executeQuery($sql, - array(), + [], FALSE, 'CRM_Contact_DAO_ContactType' ); while ($dao->fetch()) { - $value = array(); + $value = []; CRM_Core_DAO::storeValues($dao, $value); if (array_key_exists('parent_id', $value)) { $value['parent'] = $dao->parent; @@ -344,7 +324,7 @@ public static function contactTypePairs($all = FALSE, $typeName = NULL, $delimit $typeName = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($typeName, CRM_Core_DAO::VALUE_SEPARATOR)); } - $pairs = array(); + $pairs = []; if ($typeName) { foreach ($typeName as $type) { if (array_key_exists($type, $types)) { @@ -378,21 +358,23 @@ public static function getSelectElements( $isSeparator = TRUE, $separator = '__' ) { + // @todo - use Cache class - ie like Civi::cache('contactTypes') static $_cache = NULL; if ($_cache === NULL) { - $_cache = array(); + $_cache = []; } $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0'; $argString .= $isSeparator ? '_1' : '_0'; $argString .= $separator; + $argString = CRM_Utils_Cache::cleanKey($argString); if (!array_key_exists($argString, $_cache)) { $cache = CRM_Utils_Cache::singleton(); $_cache[$argString] = $cache->get($argString); if (!$_cache[$argString]) { - $_cache[$argString] = array(); + $_cache[$argString] = []; $sql = " SELECT c.name as child_name , c.label as child_label , c.id as child_id, @@ -410,7 +392,7 @@ public static function getSelectElements( } $sql .= " ORDER BY c.id"; - $values = array(); + $values = []; $dao = CRM_Core_DAO::executeQuery($sql); while ($dao->fetch()) { if (!empty($dao->parent_id)) { @@ -425,12 +407,12 @@ public static function getSelectElements( } if (!isset($values[$pName])) { - $values[$pName] = array(); + $values[$pName] = []; } - $values[$pName][] = array('key' => $key, 'label' => $label); + $values[$pName][] = ['key' => $key, 'label' => $label]; } - $selectElements = array(); + $selectElements = []; foreach ($values as $pName => $elements) { foreach ($elements as $element) { $selectElements[$element['key']] = $element['label']; @@ -461,24 +443,26 @@ public static function isaSubType($subType, $ignoreCache = FALSE) { /** * Retrieve the basic contact type associated with given subType. * - * @param array /string $subType contact subType. - * @return array/string of basicTypes. + * @param array|string $subType contact subType. + * @return array|string + * basicTypes. */ public static function getBasicType($subType) { + // @todo - use Cache class - ie like Civi::cache('contactTypes') static $_cache = NULL; if ($_cache === NULL) { - $_cache = array(); + $_cache = []; } $isArray = TRUE; if ($subType && !is_array($subType)) { - $subType = array($subType); + $subType = [$subType]; $isArray = FALSE; } $argString = implode('_', $subType); if (!array_key_exists($argString, $_cache)) { - $_cache[$argString] = array(); + $_cache[$argString] = []; $sql = " SELECT subtype.name as contact_subtype, type.name as contact_type @@ -539,7 +523,7 @@ public static function isExtendsContactType($subType, $contactType, $ignoreCache * of contactTypes */ public static function getCreateNewList() { - $shortCuts = array(); + $shortCuts = []; //@todo FIXME - using the CRM_Core_DAO::VALUE_SEPARATOR creates invalid html - if you can find the form // this is loaded onto then replace with something like '__' & test $separator = CRM_Core_DAO::VALUE_SEPARATOR; @@ -552,12 +536,12 @@ public static function getCreateNewList() { if ($csType = CRM_Utils_Array::value('1', $typeValue)) { $typeUrl .= "&cst=$csType"; } - $shortCut = array( + $shortCut = [ 'path' => 'civicrm/contact/add', 'query' => "$typeUrl&reset=1", 'ref' => "new-$value", 'title' => $value, - ); + ]; if ($csType = CRM_Utils_Array::value('1', $typeValue)) { $shortCuts[$cType]['shortCuts'][] = $shortCut; } @@ -583,7 +567,7 @@ public static function del($contactTypeId) { return FALSE; } - $params = array('id' => $contactTypeId); + $params = ['id' => $contactTypeId]; self::retrieve($params, $typeInfo); $name = $typeInfo['name']; // check if any custom group @@ -614,9 +598,10 @@ public static function del($contactTypeId) { DELETE FROM civicrm_navigation WHERE name = %1"; - $params = array(1 => array("New $name", 'String')); - $dao = CRM_Core_DAO::executeQuery($sql, $params); + $params = [1 => ["New $name", 'String']]; + CRM_Core_DAO::executeQuery($sql, $params); CRM_Core_BAO_Navigation::resetNavigation(); + Civi::cache('contactTypes')->clear(); } return TRUE; } @@ -654,34 +639,31 @@ public static function add(&$params) { } if (!empty($params['id'])) { - $params = array('name' => "New $contactName"); - $newParams = array( + $newParams = [ 'label' => "New $contact", 'is_active' => $active, - ); - CRM_Core_BAO_Navigation::processUpdate($params, $newParams); + ]; + CRM_Core_BAO_Navigation::processUpdate(['name' => "New $contactName"], $newParams); } else { $name = self::getBasicType($contactName); if (!$name) { - return; + return NULL; } - $value = array('name' => "New $name"); + $value = ['name' => "New $name"]; CRM_Core_BAO_Navigation::retrieve($value, $navinfo); - $navigation = array( + $navigation = [ 'label' => "New $contact", 'name' => "New $contactName", 'url' => "civicrm/contact/add?ct=$name&cst=$contactName&reset=1", 'permission' => 'add contacts', 'parent_id' => $navinfo['id'], 'is_active' => $active, - ); + ]; CRM_Core_BAO_Navigation::add($navigation); } CRM_Core_BAO_Navigation::resetNavigation(); - - // reset the cache after adding - self::subTypeInfo(NULL, FALSE, FALSE, TRUE); + Civi::cache('contactTypes')->clear(); return $contactType; } @@ -694,14 +676,14 @@ public static function add(&$params) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { - $params = array('id' => $id); + $params = ['id' => $id]; self::retrieve($params, $contactinfo); - $params = array('name' => "New $contactinfo[name]"); - $newParams = array('is_active' => $is_active); + $params = ['name' => "New $contactinfo[name]"]; + $newParams = ['is_active' => $is_active]; CRM_Core_BAO_Navigation::processUpdate($params, $newParams); CRM_Core_BAO_Navigation::resetNavigation(); return CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_ContactType', $id, @@ -784,7 +766,6 @@ public static function hasCustomData($contactType, $contactId = NULL) { $customDataCount = CRM_Core_DAO::singleValueQuery($sql); if (!empty($customDataCount)) { - $dao->free(); return TRUE; } } @@ -834,12 +815,12 @@ public static function hasRelationships($contactId, $contactType) { * * @return array */ - public static function getSubtypeCustomPair($contactType, $subtypeSet = array()) { + public static function getSubtypeCustomPair($contactType, $subtypeSet = []) { if (empty($subtypeSet)) { return $subtypeSet; } - $customSet = $subTypeClause = array(); + $customSet = $subTypeClause = []; foreach ($subtypeSet as $subtype) { $subtype = CRM_Utils_Type::escape($subtype, 'String'); $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR; @@ -848,7 +829,7 @@ public static function getSubtypeCustomPair($contactType, $subtypeSet = array()) $query = "SELECT table_name FROM civicrm_custom_group WHERE extends = %1 AND " . implode(" OR ", $subTypeClause); - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($contactType, 'String'))); + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$contactType, 'String']]); while ($dao->fetch()) { $customSet[] = $dao->table_name; } @@ -869,8 +850,8 @@ public static function getSubtypeCustomPair($contactType, $subtypeSet = array()) public static function deleteCustomSetForSubtypeMigration( $contactID, $contactType, - $oldSubtypeSet = array(), - $newSubtypeSet = array() + $oldSubtypeSet = [], + $newSubtypeSet = [] ) { $oldCustomSet = self::getSubtypeCustomPair($contactType, $oldSubtypeSet); $newCustomSet = self::getSubtypeCustomPair($contactType, $newSubtypeSet); @@ -891,10 +872,11 @@ public static function deleteCustomSetForSubtypeMigration( * Custom group id. * @param array $subtypes * List of subtypes related to which entry is to be removed. + * @param array $subtypesToPreserve * * @return bool */ - public static function deleteCustomRowsOfSubtype($gID, $subtypes = array(), $subtypesToPreserve = array()) { + public static function deleteCustomRowsOfSubtype($gID, $subtypes = [], $subtypesToPreserve = []) { if (!$gID or empty($subtypes)) { return FALSE; } @@ -910,7 +892,7 @@ public static function deleteCustomRowsOfSubtype($gID, $subtypes = array(), $sub } $subtypesToPreserveClause = implode(' AND ', $subtypesToPreserveClause); - $subtypeClause = array(); + $subtypeClause = []; foreach ($subtypes as $subtype) { $subtype = CRM_Utils_Type::escape($subtype, 'String'); $subtypeClause[] = "( civicrm_contact.contact_sub_type LIKE '%" . CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR . "%'" @@ -942,7 +924,7 @@ public static function deleteCustomRowsOfSubtype($gID, $subtypes = array(), $sub public static function deleteCustomRowsForEntityID($customTable, $entityID) { $customTable = CRM_Utils_Type::escape($customTable, 'String'); $query = "DELETE FROM {$customTable} WHERE entity_id = %1"; - return CRM_Core_DAO::singleValueQuery($query, array(1 => array($entityID, 'Integer'))); + return CRM_Core_DAO::singleValueQuery($query, [1 => [$entityID, 'Integer']]); } } diff --git a/CRM/Contact/BAO/DashboardContact.php b/CRM/Contact/BAO/DashboardContact.php index 734a4e5f656a..f2b3e0ac0f45 100644 --- a/CRM/Contact/BAO/DashboardContact.php +++ b/CRM/Contact/BAO/DashboardContact.php @@ -1,9 +1,9 @@ delete(); // make all the 'add_to_group_id' field of 'civicrm_uf_group table', pointing to this group, as null - $params = array(1 => array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; $query = "UPDATE civicrm_uf_group SET `add_to_group_id`= NULL WHERE `add_to_group_id` = %1"; CRM_Core_DAO::executeQuery($query, $params); @@ -117,10 +117,10 @@ public static function discard($id) { CRM_Utils_Hook::post('delete', 'Group', $id, $group); // delete the recently created Group - $groupRecent = array( + $groupRecent = [ 'id' => $id, 'type' => 'Group', - ); + ]; CRM_Utils_Recent::del($groupRecent); } @@ -130,8 +130,8 @@ public static function discard($id) { * @param int $id */ public static function getGroupContacts($id) { - $params = array(array('group', 'IN', array(1 => $id), 0, 0)); - list($contacts, $_) = CRM_Contact_BAO_Query::apiQuery($params, array('contact_id')); + $params = [['group', 'IN', [1 => $id], 0, 0]]; + list($contacts, $_) = CRM_Contact_BAO_Query::apiQuery($params, ['contact_id']); return $contacts; } @@ -149,7 +149,7 @@ public static function getGroupContacts($id) { */ public static function memberCount($id, $status = 'Added', $countChildGroups = FALSE) { $groupContact = new CRM_Contact_DAO_GroupContact(); - $groupIds = array($id); + $groupIds = [$id]; if ($countChildGroups) { $groupIds = CRM_Contact_BAO_GroupNesting::getDescendentGroupIds($groupIds); } @@ -195,11 +195,11 @@ public static function memberCount($id, $status = 'Added', $countChildGroups = F * this array contains the list of members for this group id */ public static function getMember($groupID, $useCache = TRUE, $limit = 0) { - $params = array(array('group', '=', $groupID, 0, 0)); - $returnProperties = array('contact_id'); + $params = [['group', '=', $groupID, 0, 0]]; + $returnProperties = ['contact_id']; list($contacts) = CRM_Contact_BAO_Query::apiQuery($params, $returnProperties, NULL, NULL, 0, $limit, $useCache); - $aMembers = array(); + $aMembers = []; foreach ($contacts as $contact) { $aMembers[$contact['contact_id']] = 1; } @@ -278,7 +278,7 @@ public static function getGroups( $flag = $returnProperties && in_array('member_count', $returnProperties) ? 1 : 0; - $groups = array(); + $groups = []; while ($dao->fetch()) { $group = new CRM_Contact_DAO_Group(); if ($flag) { @@ -350,22 +350,33 @@ public static function create(&$params) { CRM_Utils_Hook::pre('create', 'Group', NULL, $params); } + // dev/core#287 Disable child groups if all parents are disabled. + if (!empty($params['id'])) { + $allChildGroupIds = self::getChildGroupIds($params['id']); + foreach ($allChildGroupIds as $childKey => $childValue) { + $parentIds = CRM_Contact_BAO_GroupNesting::getParentGroupIds($childValue); + $activeParentsCount = civicrm_api3('Group', 'getcount', [ + 'id' => ['IN' => $parentIds], + 'is_active' => 1, + ]); + if (count($parentIds) >= 1 && $activeParentsCount <= 1) { + $setDisable = self::setIsActive($childValue, CRM_Utils_Array::value('is_active', $params, 1)); + } + } + } // form the name only if missing: CRM-627 $nameParam = CRM_Utils_Array::value('name', $params, NULL); if (!$nameParam && empty($params['id'])) { $params['name'] = CRM_Utils_String::titleToVar($params['title']); } + if (!empty($params['parents'])) { + $params['parents'] = CRM_Utils_Array::convertCheckboxFormatToArray((array) $params['parents']); + } + // convert params if array type if (isset($params['group_type'])) { - if (is_array($params['group_type'])) { - $params['group_type'] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, - CRM_Utils_Array::convertCheckboxFormatToArray($params['group_type']) - ) . CRM_Core_DAO::VALUE_SEPARATOR; - } - else { - $params['group_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $params['group_type'] . CRM_Core_DAO::VALUE_SEPARATOR; - } + $params['group_type'] = CRM_Utils_Array::convertCheckboxFormatToArray((array) $params['group_type']); } else { $params['group_type'] = NULL; @@ -391,17 +402,8 @@ public static function create(&$params) { } } $group = new CRM_Contact_BAO_Group(); - $group->copyValues($params); - //@todo very hacky fix for the fact this function wants to receive 'parents' as an array further down but - // needs it as a separated string for the DB. Preferred approaches are having the copyParams or save fn - // use metadata to translate the array to the appropriate DB type or altering the param in the api layer, - // or at least altering the param in same section as 'group_type' rather than repeating here. However, further down - // we need the $params one to be in it's original form & we are not sure what test coverage we have on that - if (isset($group->parents) && is_array($group->parents)) { - $group->parents = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, - array_keys($group->parents) - ) . CRM_Core_DAO::VALUE_SEPARATOR; - } + $group->copyValues($params, TRUE); + if (empty($params['id']) && !$nameParam ) { @@ -419,7 +421,6 @@ public static function create(&$params) { $group->name = substr($group->name, 0, -4) . "_{$group->id}"; } - $group->buildClause(); $group->save(); // add custom field values @@ -437,14 +438,11 @@ public static function create(&$params) { ) { // if no parent present and the group doesn't already have any parents, // make sure site group goes as parent - $params['parents'] = array($domainGroupID => 1); - } - elseif (array_key_exists('parents', $params) && !is_array($params['parents'])) { - $params['parents'] = array($params['parents'] => 1); + $params['parents'] = [$domainGroupID]; } if (!empty($params['parents'])) { - foreach ($params['parents'] as $parentId => $dnc) { + foreach ($params['parents'] as $parentId) { if ($parentId && !CRM_Contact_BAO_GroupNesting::isParentChild($parentId, $group->id)) { CRM_Contact_BAO_GroupNesting::add($parentId, $group->id); } @@ -463,8 +461,11 @@ public static function create(&$params) { } if (!empty($params['organization_id'])) { - $groupOrg = $params; - $groupOrg['group_id'] = $group->id; + // dev/core#382 Keeping the id here can cause db errors as it tries to update the wrong record in the Organization table + $groupOrg = [ + 'group_id' => $group->id, + 'organization_id' => $params['organization_id'], + ]; CRM_Contact_BAO_GroupOrganization::add($groupOrg); } @@ -478,7 +479,7 @@ public static function create(&$params) { CRM_Utils_Hook::post('create', 'Group', $group->id, $group); } - $recentOther = array(); + $recentOther = []; if (CRM_Core_Permission::check('edit groups')) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/group', 'reset=1&action=update&id=' . $group->id); // currently same permission we are using for delete a group @@ -499,25 +500,6 @@ public static function create(&$params) { return $group; } - /** - * Given a saved search compute the clause and the tables - * and store it for future use - */ - public function buildClause() { - $params = array(array('group', 'IN', array($this->id), 0, 0)); - - if (!empty($params)) { - $tables = $whereTables = array(); - $this->where_clause = CRM_Contact_BAO_Query::getWhereClause($params, NULL, $tables, $whereTables); - if (!empty($tables)) { - $this->select_tables = serialize($tables); - } - if (!empty($whereTables)) { - $this->where_tables = serialize($whereTables); - } - } - } - /** * Defines a new smart group. * @@ -554,8 +536,8 @@ public static function createSmartGroup(&$params) { * @param bool $isActive * Value we want to set the is_active field. * - * @return CRM_Core_DAO|null - * DAO object on success, NULL otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $isActive) { return CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Group', $id, 'is_active', $isActive); @@ -602,16 +584,11 @@ public static function groupTypeCondition($groupType = NULL, $excludeHidden = TR /** * Get permission relevant clauses. - * CRM-12209 - * - * @param bool $force * * @return array */ - public static function getPermissionClause($force = FALSE) { - static $clause = 1; - static $retrieved = FALSE; - if (!$retrieved || $force) { + public static function getPermissionClause() { + if (!isset(Civi::$statics[__CLASS__]['permission_clause'])) { if (CRM_Core_Permission::check('view all contacts') || CRM_Core_Permission::check('edit all contacts')) { $clause = 1; } @@ -626,9 +603,9 @@ public static function getPermissionClause($force = FALSE) { $clause = '1 = 0'; } } + Civi::$statics[__CLASS__]['permission_clause'] = $clause; } - $retrieved = TRUE; - return $clause; + return Civi::$statics[__CLASS__]['permission_clause']; } /** @@ -638,11 +615,12 @@ public static function getPermissionClause($force = FALSE) { */ protected static function flushCaches() { CRM_Utils_System::flushCache(); - $staticCaches = array( + $staticCaches = [ 'CRM_Core_PseudoConstant' => 'groups', 'CRM_ACL_API' => 'group_permission', 'CRM_ACL_BAO_ACL' => 'permissioned_groups', - ); + 'CRM_Contact_BAO_Group' => 'permission_clause', + ]; foreach ($staticCaches as $class => $key) { if (isset(Civi::$statics[$class][$key])) { unset(Civi::$statics[$class][$key]); @@ -676,12 +654,9 @@ public static function createHiddenSmartGroup($params) { //save the mapping for search builder if (!$ssId) { //save record in mapping table - $mappingParams = array( - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Search Builder', - 'name' - ), - ); + $mappingParams = [ + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Search Builder'), + ]; $mapping = CRM_Core_BAO_Mapping::add($mappingParams); $mappingId = $mapping->id; } @@ -700,7 +675,8 @@ public static function createHiddenSmartGroup($params) { //create/update saved search record. $savedSearch = new CRM_Contact_BAO_SavedSearch(); $savedSearch->id = $ssId; - $savedSearch->form_values = serialize($params['form_values']); + $formValues = $params['search_context'] === 'builder' ? $params['form_values'] : CRM_Contact_BAO_Query::convertFormValues($params['form_values']); + $savedSearch->form_values = serialize($formValues); $savedSearch->mapping_id = $mappingId; $savedSearch->search_custom_id = CRM_Utils_Array::value('search_custom_id', $params); $savedSearch->save(); @@ -716,14 +692,14 @@ public static function createHiddenSmartGroup($params) { } else { //create group only when new saved search. - $groupParams = array( + $groupParams = [ 'title' => "Hidden Smart Group {$ssId}", 'is_active' => CRM_Utils_Array::value('is_active', $params, 1), 'is_hidden' => CRM_Utils_Array::value('is_hidden', $params, 1), 'group_type' => CRM_Utils_Array::value('group_type', $params), 'visibility' => CRM_Utils_Array::value('visibility', $params), 'saved_search_id' => $ssId, - ); + ]; $smartGroup = self::create($groupParams); $smartGroupId = $smartGroup->id; @@ -731,19 +707,16 @@ public static function createHiddenSmartGroup($params) { // Update mapping with the name and description of the hidden smart group. if ($mappingId) { - $mappingParams = array( + $mappingParams = [ 'id' => $mappingId, 'name' => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $smartGroupId, 'name', 'id'), 'description' => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $smartGroupId, 'description', 'id'), - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Search Builder', - 'name' - ), - ); + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Search Builder'), + ]; CRM_Core_BAO_Mapping::add($mappingParams); } - return array($smartGroupId, $ssId); + return [$smartGroupId, $ssId]; } /** @@ -759,7 +732,7 @@ public static function createHiddenSmartGroup($params) { * @todo there seems little reason for the small number of functions that call this to pass in * params that then need to be translated in this function since they are coding them when calling */ - static public function getGroupListSelector(&$params) { + public static function getGroupListSelector(&$params) { // format the params $params['offset'] = ($params['page'] - 1) * $params['rp']; $params['rowCount'] = $params['rp']; @@ -778,9 +751,9 @@ static public function getGroupListSelector(&$params) { } // format params and add links - $groupList = array(); + $groupList = []; foreach ($groups as $id => $value) { - $group = array(); + $group = []; $group['group_id'] = $value['id']; $group['count'] = $value['count']; $group['title'] = $value['title']; @@ -789,12 +762,12 @@ static public function getGroupListSelector(&$params) { if (empty($params['parent_id']) && !empty($value['parents'])) { $group['parent_id'] = $value['parents']; $groupIds = explode(',', $value['parents']); - $title = array(); + $title = []; foreach ($groupIds as $gId) { $title[] = $allGroups[$gId]; } $group['title'] .= '
    ' . ts('Child of') . ': ' . implode(', ', $title) . '
    '; - $value['class'] = array_diff($value['class'], array('crm-row-parent')); + $value['class'] = array_diff($value['class'], ['crm-row-parent']); } $group['DT_RowId'] = 'row_' . $value['id']; if (empty($params['parentsOnly'])) { @@ -805,7 +778,7 @@ static public function getGroupListSelector(&$params) { } } $group['DT_RowClass'] = 'crm-entity ' . implode(' ', $value['class']); - $group['DT_RowAttr'] = array(); + $group['DT_RowAttr'] = []; $group['DT_RowAttr']['data-id'] = $value['id']; $group['DT_RowAttr']['data-entity'] = 'group'; @@ -828,7 +801,7 @@ static public function getGroupListSelector(&$params) { array_push($groupList, $group); } - $groupsDT = array(); + $groupsDT = []; $groupsDT['data'] = $groupList; $groupsDT['recordsTotal'] = !empty($params['total']) ? $params['total'] : NULL; $groupsDT['recordsFiltered'] = !empty($params['total']) ? $params['total'] : NULL; @@ -898,7 +871,7 @@ public static function getGroupList(&$params) { //FIXME CRM-4418, now we are handling delete separately //if we introduce 'delete for group' make sure to handle here. - $groupPermissions = array(CRM_Core_Permission::VIEW); + $groupPermissions = [CRM_Core_Permission::VIEW]; if (CRM_Core_Permission::check('edit groups')) { $groupPermissions[] = CRM_Core_Permission::EDIT; $groupPermissions[] = CRM_Core_Permission::DELETE; @@ -907,19 +880,19 @@ public static function getGroupList(&$params) { // CRM-9936 $reservedPermission = CRM_Core_Permission::check('administer reserved groups'); - $links = self::actionLinks(); + $links = self::actionLinks($params); $allTypes = CRM_Core_OptionGroup::values('group_type'); - $values = $groupsToCount = array(); + $values = []; $visibility = CRM_Core_SelectValues::ufVisibility(); while ($object->fetch()) { $newLinks = $links; - $values[$object->id] = array( - 'class' => array(), + $values[$object->id] = [ + 'class' => [], 'count' => '0', - ); + ]; CRM_Core_DAO::storeValues($object, $values[$object->id]); if ($object->saved_search_id) { @@ -964,30 +937,30 @@ public static function getGroupList(&$params) { $values[$object->id]['visibility'] = $visibility[$values[$object->id]['visibility']]; - $groupsToCount[$object->saved_search_id ? 'civicrm_group_contact_cache' : 'civicrm_group_contact'][] = $object->id; - if (isset($values[$object->id]['group_type'])) { $groupTypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($values[$object->id]['group_type'], 1, -1) ); - $types = array(); + $types = []; foreach ($groupTypes as $type) { $types[] = CRM_Utils_Array::value($type, $allTypes); } $values[$object->id]['group_type'] = implode(', ', $types); } - $values[$object->id]['action'] = CRM_Core_Action::formLink($newLinks, - $action, - array( - 'id' => $object->id, - 'ssid' => $object->saved_search_id, - ), - ts('more'), - FALSE, - 'group.selector.row', - 'Group', - $object->id - ); + if ($action) { + $values[$object->id]['action'] = CRM_Core_Action::formLink($newLinks, + $action, + [ + 'id' => $object->id, + 'ssid' => $object->saved_search_id, + ], + ts('more'), + FALSE, + 'group.selector.row', + 'Group', + $object->id + ); + } // If group has children, add class for link to view children $values[$object->id]['is_parent'] = FALSE; @@ -1007,29 +980,28 @@ public static function getGroupList(&$params) { $values[$object->id]['org_info'] = "{$object->org_name}"; } else { - $values[$object->id]['org_info'] = ''; // Empty cell + // Empty cell + $values[$object->id]['org_info'] = ''; } } else { - $values[$object->id]['org_info'] = NULL; // Collapsed column if all cells are NULL + // Collapsed column if all cells are NULL + $values[$object->id]['org_info'] = NULL; } if ($object->created_id) { $contactUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$object->created_id}"); $values[$object->id]['created_by'] = "{$object->created_by}"; } - } - // Get group counts - executes one query for regular groups and another for smart groups - foreach ($groupsToCount as $table => $groups) { - $where = "g.group_id IN (" . implode(',', $groups) . ")"; - if ($table == 'civicrm_group_contact') { - $where .= " AND g.status = 'Added'"; + // By default, we try to get a count of the contacts in each group + // to display to the user on the Manage Group page. However, if + // that will result in the cache being regenerated, then dipslay + // "unknown" instead to avoid a long wait for the user. + if (CRM_Contact_BAO_GroupContactCache::shouldGroupBeRefreshed($object->id)) { + $values[$object->id]['count'] = ts('unknown'); } - // Exclude deleted contacts - $where .= " and c.id = g.contact_id AND c.is_deleted = 0"; - $dao = CRM_Core_DAO::executeQuery("SELECT g.group_id, COUNT(*) as `count` FROM $table g, civicrm_contact c WHERE $where GROUP BY g.group_id"); - while ($dao->fetch()) { - $values[$dao->group_id]['count'] = $dao->count; + else { + $values[$object->id]['count'] = civicrm_api3('Contact', 'getcount', ['group' => $object->id]); } } @@ -1053,7 +1025,7 @@ public static function getGroupList(&$params) { * @param array $groupIDs * Array of group ids. * - * @param NULL $parents + * @param string $parents * @param string $spacer * @param bool $titleOnly * @@ -1066,7 +1038,7 @@ public static function getGroupsHierarchy( $titleOnly = FALSE ) { if (empty($groupIDs)) { - return array(); + return []; } $groupIdString = '(' . implode(',', array_keys($groupIDs)) . ')'; @@ -1078,18 +1050,18 @@ public static function getGroupsHierarchy( // separators in front of the name to give it a visual offset. // Instead of recursively making mysql queries, we'll make one big // query and build the hierarchy with the algorithm below. - $groups = array(); - $args = array(1 => array($groupIdString, 'String')); + $groups = []; + $args = [1 => [$groupIdString, 'String']]; $query = " SELECT id, title, description, visibility, parents FROM civicrm_group WHERE id IN $groupIdString "; if ($parents) { - // group can have > 1 parent so parents may be comma separated list (eg. '1,2,5'). We just grab and match on 1st parent. + // group can have > 1 parent so parents may be comma separated list (eg. '1,2,5'). $parentArray = explode(',', $parents); - $parent = $parentArray[0]; - $args[2] = array($parent, 'Integer'); + $parent = self::filterActiveGroups($parentArray); + $args[2] = [$parent, 'Integer']; $query .= " AND SUBSTRING_INDEX(parents, ',', 1) = %2"; } $query .= " ORDER BY title"; @@ -1099,31 +1071,30 @@ public static function getGroupsHierarchy( // $roots represent the current leaf nodes that need to be checked for // children. $rows represent the unplaced nodes // $tree contains the child nodes based on their parent_id. - $roots = array(); - $tree = array(); + $roots = []; + $tree = []; while ($dao->fetch()) { if ($dao->parents) { $parentArray = explode(',', $dao->parents); - $parent = $parentArray[0]; - $tree[$parent][] = array( + $parent = self::filterActiveGroups($parentArray); + $tree[$parent][] = [ 'id' => $dao->id, 'title' => $dao->title, 'visibility' => $dao->visibility, 'description' => $dao->description, - ); + ]; } else { - $roots[] = array( + $roots[] = [ 'id' => $dao->id, 'title' => $dao->title, 'visibility' => $dao->visibility, 'description' => $dao->description, - ); + ]; } } - $dao->free(); - $hierarchy = array(); + $hierarchy = []; for ($i = 0; $i < count($roots); $i++) { self::buildGroupHierarchy($hierarchy, $roots[$i], $tree, $titleOnly, $spacer, 0); } @@ -1149,11 +1120,11 @@ private static function buildGroupHierarchy(&$hierarchy, $group, $tree, $titleOn $hierarchy[$group['id']] = $spaces . $group['title']; } else { - $hierarchy[$group['id']] = array( + $hierarchy[$group['id']] = [ 'title' => $spaces . $group['title'], 'description' => $group['description'], 'visibility' => $group['visibility'], - ); + ]; } // For performance reasons we use a for loop rather than a foreach. @@ -1196,15 +1167,15 @@ public static function getGroupCount(&$params) { * @return string */ public static function whereClause(&$params, $sortBy = TRUE, $excludeHidden = TRUE) { - $values = array(); + $values = []; $title = CRM_Utils_Array::value('title', $params); if ($title) { $clauses[] = "groups.title LIKE %1"; if (strpos($title, '%') !== FALSE) { - $params[1] = array($title, 'String', FALSE); + $params[1] = [$title, 'String', FALSE]; } else { - $params[1] = array($title, 'String', TRUE); + $params[1] = [$title, 'String', TRUE]; } } @@ -1214,14 +1185,14 @@ public static function whereClause(&$params, $sortBy = TRUE, $excludeHidden = TR if (!empty($types)) { $clauses[] = 'groups.group_type LIKE %2'; $typeString = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $types) . CRM_Core_DAO::VALUE_SEPARATOR; - $params[2] = array($typeString, 'String', TRUE); + $params[2] = [$typeString, 'String', TRUE]; } } $visibility = CRM_Utils_Array::value('visibility', $params); if ($visibility) { $clauses[] = 'groups.visibility = %3'; - $params[3] = array($visibility, 'String'); + $params[3] = [$visibility, 'String']; } $groupStatus = CRM_Utils_Array::value('status', $params); @@ -1229,12 +1200,12 @@ public static function whereClause(&$params, $sortBy = TRUE, $excludeHidden = TR switch ($groupStatus) { case 1: $clauses[] = 'groups.is_active = 1'; - $params[4] = array($groupStatus, 'Integer'); + $params[4] = [$groupStatus, 'Integer']; break; case 2: $clauses[] = 'groups.is_active = 0'; - $params[4] = array($groupStatus, 'Integer'); + $params[4] = [$groupStatus, 'Integer']; break; case 3: @@ -1252,16 +1223,16 @@ public static function whereClause(&$params, $sortBy = TRUE, $excludeHidden = TR $parent_id = CRM_Utils_Array::value('parent_id', $params); if ($parent_id) { $clauses[] = 'groups.id IN (SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id = %5)'; - $params[5] = array($parent_id, 'Integer'); + $params[5] = [$parent_id, 'Integer']; } if ($createdBy = CRM_Utils_Array::value('created_by', $params)) { $clauses[] = "createdBy.sort_name LIKE %6"; if (strpos($createdBy, '%') !== FALSE) { - $params[6] = array($createdBy, 'String', FALSE); + $params[6] = [$createdBy, 'String', FALSE]; } else { - $params[6] = array($createdBy, 'String', TRUE); + $params[6] = [$createdBy, 'String', TRUE]; } } @@ -1284,37 +1255,42 @@ public static function whereClause(&$params, $sortBy = TRUE, $excludeHidden = TR * @return array * array of action links */ - public static function actionLinks() { - $links = array( - CRM_Core_Action::VIEW => array( - 'name' => ts('Contacts'), + public static function actionLinks($params) { + // If component_mode is set we change the "View" link to match the requested component type + if (!isset($params['component_mode'])) { + $params['component_mode'] = CRM_Contact_BAO_Query::MODE_CONTACTS; + } + $modeValue = CRM_Contact_Form_Search::getModeValue($params['component_mode']); + $links = [ + CRM_Core_Action::VIEW => [ + 'name' => $modeValue['selectorLabel'], 'url' => 'civicrm/group/search', - 'qs' => 'reset=1&force=1&context=smog&gid=%%id%%', + 'qs' => 'reset=1&force=1&context=smog&gid=%%id%%&component_mode=' . $params['component_mode'], 'title' => ts('Group Contacts'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Settings'), 'url' => 'civicrm/group', 'qs' => 'reset=1&action=update&id=%%id%%', 'title' => ts('Edit Group'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable Group'), - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable Group'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/group', 'qs' => 'reset=1&action=delete&id=%%id%%', 'title' => ts('Delete Group'), - ), - ); + ], + ]; return $links; } @@ -1356,23 +1332,65 @@ protected function assignTestValue($fieldName, &$fieldDef, $counter) { /** * Get child group ids * - * @param array $ids + * @param array $regularGroupIDs * Parent Group IDs * * @return array */ - public static function getChildGroupIds($ids) { - $notFound = FALSE; - $childGroupIds = array(); - foreach ($ids as $id) { - $childId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $id, 'children'); - while (!empty($childId)) { - $childGroupIds[] = $childId; - $childId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $childId, 'children'); + public static function getChildGroupIds($regularGroupIDs) { + $childGroupIDs = []; + + foreach ((array) $regularGroupIDs as $regularGroupID) { + // temporary store the child group ID(s) of regular group identified by $id, + // later merge with main child group array + $tempChildGroupIDs = []; + // check that the regular group has any child group, if not then continue + if ($childrenFound = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $regularGroupID, 'children')) { + $tempChildGroupIDs[] = $childrenFound; + } + else { + continue; + } + // as civicrm_group.children stores multiple group IDs in comma imploded string format, + // so we need to convert it into array of child group IDs first + $tempChildGroupIDs = explode(',', implode(',', $tempChildGroupIDs)); + $childGroupIDs = array_merge($childGroupIDs, $tempChildGroupIDs); + // recursively fetch the child group IDs + while (count($tempChildGroupIDs)) { + $tempChildGroupIDs = self::getChildGroupIds($tempChildGroupIDs); + if (count($tempChildGroupIDs)) { + $childGroupIDs = array_merge($childGroupIDs, $tempChildGroupIDs); + } + } + } + + return $childGroupIDs; + } + + /** + * Check parent groups and filter out the disabled ones. + * + * @param array $parentArray + * Array of group Ids. + * + * @return int + */ + public static function filterActiveGroups($parentArray) { + if (count($parentArray) > 1) { + $result = civicrm_api3('Group', 'get', [ + 'id' => ['IN' => $parentArray], + 'is_active' => TRUE, + 'return' => 'id', + ]); + $activeParentGroupIDs = CRM_Utils_Array::collect('id', $result['values']); + foreach ($parentArray as $key => $groupID) { + if (!array_key_exists($groupID, $activeParentGroupIDs)) { + unset($parentArray[$key]); + } } } - return $childGroupIds; + return reset($parentArray); } } diff --git a/CRM/Contact/BAO/GroupContact.php b/CRM/Contact/BAO/GroupContact.php index 8833d1958d48..bb9d9ec163ec 100644 --- a/CRM/Contact/BAO/GroupContact.php +++ b/CRM/Contact/BAO/GroupContact.php @@ -1,9 +1,9 @@ copyValues($params); - CRM_Contact_BAO_SubscriptionHistory::create($params); $groupContact->save(); + + // Lookup existing info for the sake of subscription history + if (!empty($params['id'])) { + $groupContact->find(TRUE); + $params = $groupContact->toArray(); + } + CRM_Contact_BAO_SubscriptionHistory::create($params); + + CRM_Utils_Hook::post($hook, 'GroupContact', $groupContact->id, $groupContact); + return $groupContact; } @@ -74,12 +84,7 @@ public static function add(&$params) { * @return bool */ public static function dataExists(&$params) { - // return if no data present - if ($params['group_id'] == 0) { - return FALSE; - } - - return TRUE; + return (!empty($params['id']) || (!empty($params['group_id']) && !empty($params['contact_id']))); } /** @@ -135,7 +140,7 @@ public static function addContactsToGroup( $tracking = NULL ) { if (empty($contactIds) || empty($groupId)) { - return array(); + return []; } CRM_Utils_Hook::pre('create', 'GroupContact', $groupId, $contactIds); @@ -143,19 +148,11 @@ public static function addContactsToGroup( list($numContactsAdded, $numContactsNotAdded) = self::bulkAddContactsToGroup($contactIds, $groupId, $method, $status, $tracking); - // also reset the acl cache - $config = CRM_Core_Config::singleton(); - if (!$config->doNotResetCache) { - CRM_ACL_BAO_Cache::resetCache(); - } - - // reset the group contact cache for all group(s) - // if this group is being used as a smart group - CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); + CRM_Contact_BAO_Contact_Utils::clearContactCaches(); CRM_Utils_Hook::post('create', 'GroupContact', $groupId, $contactIds); - return array(count($contactIds), $numContactsAdded, $numContactsNotAdded); + return [count($contactIds), $numContactsAdded, $numContactsNotAdded]; } /** @@ -168,7 +165,7 @@ public static function addContactsToGroup( * * @param string $method * @param string $status - * @param NULL $tracking + * @param string $tracking * * @return array * (total, removed, notRemoved) count of contacts removed to group @@ -181,9 +178,8 @@ public static function removeContactsFromGroup( $tracking = NULL ) { if (!is_array($contactIds)) { - return array(0, 0, 0); + return [0, 0, 0]; } - if ($status == 'Removed' || $status == 'Deleted') { $op = 'delete'; } @@ -203,17 +199,24 @@ public static function removeContactsFromGroup( foreach ($contactIds as $contactId) { if ($status == 'Deleted') { - $query = "DELETE FROM civicrm_group_contact WHERE contact_id=$contactId AND group_id=$groupId"; - $dao = CRM_Core_DAO::executeQuery($query); - $historyParams = array( + $query = "DELETE FROM civicrm_group_contact WHERE contact_id = %1 AND group_id = %2"; + $dao = CRM_Core_DAO::executeQuery($query, [ + 1 => [$contactId, 'Positive'], + 2 => [$groupId, 'Positive'], + ]); + $historyParams = [ 'group_id' => $groupId, 'contact_id' => $contactId, 'status' => $status, 'method' => $method, 'date' => $date, 'tracking' => $tracking, - ); + ]; CRM_Contact_BAO_SubscriptionHistory::create($historyParams); + // Removing a row from civicrm_group_contact for a smart group may mean a contact + // Is now back in a group based on criteria so we will invalidate the cache if it is there + // So that accurate group cache is created next time it is needed. + CRM_Contact_BAO_GroupContactCache::invalidateGroupContactCache($groupId); } else { $groupContact = new CRM_Contact_DAO_GroupContact(); @@ -231,39 +234,27 @@ public static function removeContactsFromGroup( } //now we grant the negative membership to contact if not member. CRM-3711 - $historyParams = array( + $historyParams = [ 'group_id' => $groupId, 'contact_id' => $contactId, 'status' => $status, 'method' => $method, 'date' => $date, 'tracking' => $tracking, - ); + ]; CRM_Contact_BAO_SubscriptionHistory::create($historyParams); $groupContact->status = $status; $groupContact->save(); + // Remove any rows from the group contact cache so it disappears straight away from smart groups. + CRM_Contact_BAO_GroupContactCache::removeContact($contactId, $groupId); } } - // also reset the acl cache - $config = CRM_Core_Config::singleton(); - if (!$config->doNotResetCache) { - CRM_ACL_BAO_Cache::resetCache(); - } - - // reset the group contact cache for all group(s) - // if this group is being used as a smart group - // @todo consider what to do here - it feels like we should either - // 1) just invalidate the specific group's cache(& perhaps any parents) & let cron do it's thing or - // possibly clear this specific groups cache, or just call opportunisticCacheFlush() - which would have the - // same effect as the remove call. The reservation about that is that it is no more aggressive for the group that - // we know is altered than for all the others, or perhaps, more the point with it's parents & groups that use it in - // their criteria. - CRM_Contact_BAO_GroupContactCache::remove(); + CRM_Contact_BAO_Contact_Utils::clearContactCaches(); CRM_Utils_Hook::post($op, 'GroupContact', $groupId, $contactIds); - return array(count($contactIds), $numContactsRemoved, $numContactsNotRemoved); + return [count($contactIds), $numContactsRemoved, $numContactsNotRemoved]; } /** @@ -302,7 +293,7 @@ public static function getGroupList($contactId = 0, $visibility = FALSE) { $group->query($sql); - $values = array(); + $values = []; while ($group->fetch()) { $values[$group->id] = $group->title; } @@ -335,7 +326,7 @@ public static function getGroupList($contactId = 0, $visibility = FALSE) { * @param bool $includeSmartGroups * Include or Exclude Smart Group(s) * - * @return array|int $values + * @return array|int * the relevant data object values for the contact or the total count when $count is TRUE */ public static function getContactGroup( @@ -371,21 +362,21 @@ public static function getContactGroup( if ($excludeHidden) { $where .= " AND civicrm_group.is_hidden = 0 "; } - $params = array(1 => array($contactId, 'Integer')); + $params = [1 => [$contactId, 'Integer']]; if (!empty($status)) { $where .= ' AND civicrm_group_contact.status = %2'; - $params[2] = array($status, 'String'); + $params[2] = [$status, 'String']; } if (!empty($groupId)) { $where .= " AND civicrm_group.id = %3 "; - $params[3] = array($groupId, 'Integer'); + $params[3] = [$groupId, 'Integer']; } - $tables = array( + $tables = [ 'civicrm_group_contact' => 1, 'civicrm_group' => 1, 'civicrm_subscription_history' => 1, - ); - $whereTables = array(); + ]; + $whereTables = []; if ($ignorePermission) { $permission = ' ( 1 ) '; } @@ -418,7 +409,7 @@ public static function getContactGroup( } else { $dao = CRM_Core_DAO::executeQuery($sql, $params); - $values = array(); + $values = []; while ($dao->fetch()) { $id = $dao->civicrm_group_contact_id; $values[$id]['id'] = $id; @@ -487,10 +478,10 @@ public static function getMembershipDetail($contactId, $groupID, $method = 'Emai $orderBy "; - $params = array( - 1 => array($contactId, 'Integer'), - 2 => array($groupID, 'Integer'), - ); + $params = [ + 1 => [$contactId, 'Integer'], + 2 => [$groupID, 'Integer'], + ]; $dao = CRM_Core_DAO::executeQuery($query, $params); $dao->fetch(); return $dao; @@ -526,7 +517,7 @@ public static function getGroupId($groupContactID) { * @param string $method */ public static function create(&$params, $contactId, $visibility = FALSE, $method = 'Admin') { - $contactIds = array(); + $contactIds = []; $contactIds[] = $contactId; //if $visibility is true we are coming in via profile mean $method = 'Web' @@ -552,12 +543,12 @@ public static function create(&$params, $contactId, $visibility = FALSE, $method // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input if (!is_array($params)) { - $params = array(); + $params = []; } // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input if (!isset($contactGroup) || !is_array($contactGroup)) { - $contactGroup = array(); + $contactGroup = []; } // check which values has to be add/remove contact from group @@ -586,11 +577,11 @@ public static function isContactInGroup($contactID, $groupID) { return FALSE; } - $params = array( - array('group', 'IN', array($groupID), 0, 0), - array('contact_id', '=', $contactID, 0, 0), - ); - list($contacts, $_) = CRM_Contact_BAO_Query::apiQuery($params, array('contact_id')); + $params = [ + ['group', 'IN', [$groupID], 0, 0], + ['contact_id', '=', $contactID, 0, 0], + ]; + list($contacts, $_) = CRM_Contact_BAO_Query::apiQuery($params, ['contact_id']); if (!empty($contacts)) { return TRUE; @@ -612,10 +603,10 @@ public static function isContactInGroup($contactID, $groupID) { * TODO: use the 3rd $sqls param to append sql statements rather than executing them here */ public static function mergeGroupContact($mainContactId, $otherContactId) { - $params = array( - 1 => array($mainContactId, 'Integer'), - 2 => array($otherContactId, 'Integer'), - ); + $params = [ + 1 => [$mainContactId, 'Integer'], + 2 => [$otherContactId, 'Integer'], + ]; // find all groups that are in otherContactID but not in mainContactID, copy them over $sql = " @@ -627,7 +618,7 @@ public static function mergeGroupContact($mainContactId, $otherContactId) { "; $dao = CRM_Core_DAO::executeQuery($sql, $params); - $otherGroupIDs = array(); + $otherGroupIDs = []; while ($dao->fetch()) { $otherGroupIDs[] = $dao->group_id; } @@ -662,7 +653,7 @@ public static function mergeGroupContact($mainContactId, $otherContactId) { "; $dao = CRM_Core_DAO::executeQuery($sql, $params); - $groupIDs = array(); + $groupIDs = []; while ($dao->fetch()) { // only copy it over if it has added status and migrate the history if ($dao->group_status == 'Added') { @@ -715,7 +706,7 @@ public static function mergeGroupContact($mainContactId, $otherContactId) { * The id of the group. * @param string $method * @param string $status - * @param NULL $tracking + * @param string $tracking * * @return array * (total, added, notAdded) count of contacts added to group @@ -755,19 +746,19 @@ public static function bulkAddContactsToGroup( AND status = %2 AND contact_id IN ( $contactStr ) "; - $params = array( - 1 => array($groupID, 'Integer'), - 2 => array($status, 'String'), - ); + $params = [ + 1 => [$groupID, 'Integer'], + 2 => [$status, 'String'], + ]; - $presentIDs = array(); + $presentIDs = []; $dao = CRM_Core_DAO::executeQuery($sql, $params); if ($dao->fetch()) { $presentIDs = explode(',', $dao->contactStr); $presentIDs = array_flip($presentIDs); } - $gcValues = $shValues = array(); + $gcValues = $shValues = []; foreach ($input as $cid) { if (isset($presentIDs[$cid])) { $numContactsNotAdded++; @@ -788,7 +779,7 @@ public static function bulkAddContactsToGroup( } } - return array($numContactsAdded, $numContactsNotAdded); + return [$numContactsAdded, $numContactsNotAdded]; } /** @@ -803,10 +794,9 @@ public static function bulkAddContactsToGroup( * * @return array|bool */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { - $params = array(); + public static function buildOptions($fieldName, $context = NULL, $props = []) { - $options = CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context); + $options = CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $props, $context); // Sort group list by hierarchy // TODO: This will only work when api.entity is "group_contact". What about others? diff --git a/CRM/Contact/BAO/GroupContactCache.php b/CRM/Contact/BAO/GroupContactCache.php index 409f9e91e5fb..25f69b01a356 100644 --- a/CRM/Contact/BAO/GroupContactCache.php +++ b/CRM/Contact/BAO/GroupContactCache.php @@ -1,9 +1,9 @@ ts('Opportunistic Flush'), // Flush expired caches via background cron jobs. 'deterministic' => ts('Cron Flush'), - ); + ]; } /** @@ -122,7 +122,7 @@ public static function groupRefreshedClause($groupIDClause = NULL, $includeHidde */ public static function shouldGroupBeRefreshed($groupID, $includeHiddenGroups = FALSE) { $query = self::groupRefreshedClause("g.id = %1", $includeHiddenGroups); - $params = array(1 => array($groupID, 'Integer')); + $params = [1 => [$groupID, 'Integer']]; // if the query returns the group ID, it means the group is a valid candidate for refreshing return CRM_Core_DAO::singleValueQuery($query, $params); @@ -146,11 +146,11 @@ public static function loadAll($groupIDs = NULL, $limit = 0) { // this function is expensive and should be sparingly used if groupIDs is empty if (empty($groupIDs)) { $groupIDClause = NULL; - $groupIDs = array(); + $groupIDs = []; } else { if (!is_array($groupIDs)) { - $groupIDs = array($groupIDs); + $groupIDs = [$groupIDs]; } // note escapeString is a must here and we can't send the imploded value as second argument to @@ -175,7 +175,7 @@ public static function loadAll($groupIDs = NULL, $limit = 0) { "; $dao = CRM_Core_DAO::executeQuery($query); - $processGroupIDs = array(); + $processGroupIDs = []; $refreshGroupIDs = $groupIDs; while ($dao->fetch()) { $processGroupIDs[] = $dao->id; @@ -211,22 +211,19 @@ public static function loadAll($groupIDs = NULL, $limit = 0) { } /** - * Build the smart group cache for a given group. + * Build the smart group cache for given groups. * - * @param int $groupID + * @param array $groupIDs */ - public static function add($groupID) { - // first delete the current cache - self::remove($groupID); - if (!is_array($groupID)) { - $groupID = array($groupID); - } + public static function add($groupIDs) { + $groupIDs = (array) $groupIDs; - $returnProperties = array('contact_id'); - foreach ($groupID as $gid) { - $params = array(array('group', 'IN', array($gid), 0, 0)); + foreach ($groupIDs as $groupID) { + // first delete the current cache + self::clearGroupContactCache($groupID); + $params = [['group', 'IN', [$groupID], 0, 0]]; // the below call updates the cache table as a byproduct of the query - CRM_Contact_BAO_Query::apiQuery($params, $returnProperties, NULL, NULL, 0, 0, FALSE); + CRM_Contact_BAO_Query::apiQuery($params, ['contact_id'], NULL, NULL, 0, 0, FALSE); } } @@ -236,10 +233,10 @@ public static function add($groupID) { * @todo review use of INSERT IGNORE. This function appears to be slower that inserting * with a left join. Also, 200 at once seems too little. * - * @param int $groupID + * @param array $groupID * @param array $values */ - public static function store(&$groupID, &$values) { + public static function store($groupID, &$values) { $processed = FALSE; // sort the values so we put group IDs in front and hence optimize @@ -286,130 +283,51 @@ public static function updateCacheTime($groupID, $processed) { } /** - * Removes all the cache entries pertaining to a specific group. + * @deprecated function - the best function to call is + * CRM_Contact_BAO_Contact::updateContactCache at the moment, or api job.group_cache_flush + * to really force a flush. * - * If no groupID is passed in, removes cache entries for all groups - * Has an optimization to bypass repeated invocations of this function. - * Note that this function is an advisory, i.e. the removal respects the - * cache date, i.e. the removal is not done if the group was recently - * loaded into the cache. + * Remove this function altogether by mid 2018. * - * In fact it turned out there is little overlap between the code when group is passed in - * and group is not so it makes more sense as separate functions. - * - * @todo remove last call to this function from outside the class then make function protected, - * enforce groupID as an array & remove non group handling. + * However, if updating code outside core to use this (or any BAO function) it is recommended that + * you add an api call to lock in into our contract. Currently there is not really a supported + * method for non core functions. + */ + public static function remove() { + Civi::log() + ->warning('Deprecated code. This function should not be called without groupIDs. Extensions can use the api job.group_cache_flush for a hard flush or add an api option for soft flush', ['civi.tag' => 'deprecated']); + CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); + } + + /** + * Function to clear group contact cache and reset the corresponding + * group's cache and refresh date * * @param int $groupID - * the groupID to delete cache entries, NULL for all groups. - * @param bool $onceOnly - * run the function exactly once for all groups. + * */ - public static function remove($groupID = NULL, $onceOnly = TRUE) { - static $invoked = FALSE; - - // typically this needs to happy only once per instance - // this is especially TRUE in import, where we don't need - // to do this all the time - // this optimization is done only when no groupID is passed - // i.e. cache is reset for all groups - if ( - $onceOnly && - $invoked && - $groupID == NULL - ) { - return; - } + public static function clearGroupContactCache($groupID) { + $transaction = new CRM_Core_Transaction(); + $query = " + DELETE g + FROM civicrm_group_contact_cache g + WHERE g.group_id = %1 "; - if ($groupID == NULL) { - $invoked = TRUE; - } - elseif (is_array($groupID)) { - foreach ($groupID as $gid) { - unset(self::$_alreadyLoaded[$gid]); - } - } - elseif ($groupID && array_key_exists($groupID, self::$_alreadyLoaded)) { - unset(self::$_alreadyLoaded[$groupID]); - } + $update = " + UPDATE civicrm_group g + SET cache_date = null, refresh_date = null + WHERE id = %1 "; - $refresh = NULL; - $smartGroupCacheTimeout = self::smartGroupCacheTimeout(); - $params = array( - 1 => array(self::getCacheInvalidDateTime(), 'String'), - 2 => array(self::getRefreshDateTime(), 'String'), - ); - - if (!isset($groupID)) { - if ($smartGroupCacheTimeout == 0) { - $query = " -DELETE FROM civicrm_group_contact_cache -"; - $update = " -UPDATE civicrm_group g -SET cache_date = null, - refresh_date = null -"; - } - else { - - $query = " -DELETE gc -FROM civicrm_group_contact_cache gc -INNER JOIN civicrm_group g ON g.id = gc.group_id -WHERE g.cache_date <= %1 -"; - $update = " -UPDATE civicrm_group g -SET cache_date = null, - refresh_date = null -WHERE g.cache_date <= %1 -"; - $refresh = " -UPDATE civicrm_group g -SET refresh_date = %2 -WHERE g.cache_date < %1 -AND refresh_date IS NULL -"; - } - } - elseif (is_array($groupID)) { - $groupIDs = implode(', ', $groupID); - $query = " -DELETE g -FROM civicrm_group_contact_cache g -WHERE g.group_id IN ( $groupIDs ) -"; - $update = " -UPDATE civicrm_group g -SET cache_date = null, - refresh_date = null -WHERE id IN ( $groupIDs ) -"; - } - else { - $query = " -DELETE g -FROM civicrm_group_contact_cache g -WHERE g.group_id = %1 -"; - $update = " -UPDATE civicrm_group g -SET cache_date = null, - refresh_date = null -WHERE id = %1 -"; - $params = array(1 => array($groupID, 'Integer')); - } + $params = [ + 1 => [$groupID, 'Integer'], + ]; CRM_Core_DAO::executeQuery($query, $params); - - if ($refresh) { - CRM_Core_DAO::executeQuery($refresh, $params); - } - // also update the cache_date for these groups CRM_Core_DAO::executeQuery($update, $params); + unset(self::$_alreadyLoaded[$groupID]); + + $transaction->commit(); } /** @@ -428,7 +346,7 @@ protected static function flushCaches() { // Someone else is kindly doing the refresh for us right now. return; } - $params = array(1 => array(self::getCacheInvalidDateTime(), 'String')); + $params = [1 => [self::getCacheInvalidDateTime(), 'String']]; // @todo this is consistent with previous behaviour but as the first query could take several seconds the second // could become inaccurate. It seems to make more sense to fetch them first & delete from an array (which would // also reduce joins). If we do this we should also consider how best to iterate the groups. If we do them one at @@ -475,8 +393,8 @@ protected static function flushCaches() { * @throws \CRM_Core_Exception */ protected static function getLockForRefresh() { - if (!isset(Civi::$statics[__CLASS__])) { - Civi::$statics[__CLASS__] = array('is_refresh_init' => FALSE); + if (!isset(Civi::$statics[__CLASS__]['is_refresh_init'])) { + Civi::$statics[__CLASS__] = ['is_refresh_init' => FALSE]; } if (Civi::$statics[__CLASS__]['is_refresh_init']) { @@ -493,8 +411,9 @@ protected static function getLockForRefresh() { /** * Do an opportunistic cache refresh if the site is configured for these. * - * Sites that do not run the smart group clearing cron job should refresh the caches under an opportunistic mode, akin - * to a poor man's cron. The user session will be forced to wait on this so it is less desirable. + * Sites that do not run the smart group clearing cron job should refresh the + * caches on demand. The user session will be forced to wait so it is less + * ideal. */ public static function opportunisticCacheFlush() { if (Civi::settings()->get('smart_group_cache_refresh_mode') == 'opportunistic') { @@ -529,7 +448,7 @@ public static function deterministicCacheFlush() { * TRUE if successful. */ public static function removeContact($cid, $groupId = NULL) { - $cids = array(); + $cids = []; // sanitize input foreach ((array) $cid as $c) { $cids[] = CRM_Utils_Type::escape($c, 'Integer'); @@ -595,7 +514,7 @@ public static function load(&$group, $force = FALSE) { CRM_Contact_BAO_ProximityQuery::fixInputParams($ssParams); } - $returnProperties = array(); + $returnProperties = []; if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $savedSearchID, 'mapping_id')) { $fv = CRM_Contact_BAO_SavedSearch::getFormValues($savedSearchID); $returnProperties = CRM_Core_BAO_Mapping::returnProperties($fv); @@ -636,15 +555,12 @@ public static function load(&$group, $force = FALSE) { ); $query->_useDistinct = FALSE; $query->_useGroupBy = FALSE; - $searchSQL - = $query->searchQuery( + $sqlParts = $query->getSearchSQLParts( 0, 0, NULL, FALSE, FALSE, - FALSE, TRUE, - TRUE, - NULL, NULL, NULL, - TRUE + FALSE, TRUE ); + $searchSQL = "{$sqlParts['select']} {$sqlParts['from']} {$sqlParts['where']} {$sqlParts['having']} {$sqlParts['group_by']}"; } $groupID = CRM_Utils_Type::escape($groupID, 'Integer'); $sql = $searchSQL . " AND contact_a.id NOT IN ( @@ -665,11 +581,11 @@ public static function load(&$group, $force = FALSE) { WHERE civicrm_group_contact.status = 'Added' AND civicrm_group_contact.group_id = $groupID "; - $groupIDs = array($groupID); - self::remove($groupIDs); + self::clearGroupContactCache($groupID); + $processed = FALSE; $tempTable = 'civicrm_temp_group_contact_cache' . rand(0, 2000); - foreach (array($sql, $sqlB) as $selectSql) { + foreach ([$sql, $sqlB] as $selectSql) { if (!$selectSql) { continue; } @@ -683,7 +599,7 @@ public static function load(&$group, $force = FALSE) { CRM_Core_DAO::executeQuery(" DROP TEMPORARY TABLE $tempTable"); } - self::updateCacheTime($groupIDs, $processed); + self::updateCacheTime([$groupID], $processed); if ($group->children) { @@ -694,7 +610,7 @@ public static function load(&$group, $force = FALSE) { WHERE civicrm_group_contact.status = 'Removed' AND civicrm_group_contact.group_id = $groupID "; $dao = CRM_Core_DAO::executeQuery($sql); - $removed_contacts = array(); + $removed_contacts = []; while ($dao->fetch()) { $removed_contacts[] = $dao->contact_id; } @@ -706,12 +622,12 @@ public static function load(&$group, $force = FALSE) { foreach ($removed_contacts as $removed_contact) { unset($contactIDs[$removed_contact]); } - $values = array(); + $values = []; foreach ($contactIDs as $contactID => $dontCare) { $values[] = "({$groupID},{$contactID})"; } - self::store($groupIDs, $values); + self::store([$groupID], $values); } } @@ -763,7 +679,7 @@ public static function contactGroup($contactID, $showHidden = FALSE) { $contactIDs = $contactID; } else { - $contactIDs = array($contactID); + $contactIDs = [$contactID]; } self::loadAll(); @@ -784,7 +700,7 @@ public static function contactGroup($contactID, $showHidden = FALSE) { "; $dao = CRM_Core_DAO::executeQuery($sql); - $contactGroup = array(); + $contactGroup = []; $prevContactID = NULL; while ($dao->fetch()) { if ( @@ -796,16 +712,16 @@ public static function contactGroup($contactID, $showHidden = FALSE) { $prevContactID = $dao->contact_id; if (!array_key_exists($dao->contact_id, $contactGroup)) { $contactGroup[$dao->contact_id] - = array('group' => array(), 'groupTitle' => array()); + = ['group' => [], 'groupTitle' => []]; } $contactGroup[$dao->contact_id]['group'][] - = array( + = [ 'id' => $dao->group_id, 'title' => $dao->title, 'description' => $dao->description, 'children' => $dao->children, - ); + ]; $contactGroup[$dao->contact_id]['groupTitle'][] = $dao->title; } @@ -844,4 +760,16 @@ public static function getRefreshDateTime() { return date('YmdHis', strtotime("+ " . self::smartGroupCacheTimeout() . " Minutes")); } + /** + * Invalidates the smart group cache for a particular group + * @param int $groupID - Group to invalidate + */ + public static function invalidateGroupContactCache($groupID) { + CRM_Core_DAO::executeQuery("UPDATE civicrm_group + SET cache_date = NULL, refresh_date = NULL + WHERE id = %1", [ + 1 => [$groupID, 'Positive'], + ]); + } + } diff --git a/CRM/Contact/BAO/GroupNesting.php b/CRM/Contact/BAO/GroupNesting.php index 284aa3a1bf40..dae7b4a3fd2e 100644 --- a/CRM/Contact/BAO/GroupNesting.php +++ b/CRM/Contact/BAO/GroupNesting.php @@ -1,7 +1,7 @@ copyValues($params); + if (empty($params['id'])) { + $dao->find(TRUE); + } + $dao->save(); + CRM_Utils_Hook::post($hook, 'GroupNesting', $dao->id, $dao); + return $dao; + } + /** * Adds a new group nesting record. * @@ -124,12 +146,12 @@ public static function hasParentGroups($groupId) { */ public static function getChildGroupIds($groupIds) { if (!is_array($groupIds)) { - $groupIds = array($groupIds); + $groupIds = [$groupIds]; } $dao = new CRM_Contact_DAO_GroupNesting(); $query = "SELECT child_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")"; $dao->query($query); - $childGroupIds = array(); + $childGroupIds = []; while ($dao->fetch()) { $childGroupIds[] = $dao->child_group_id; } @@ -147,12 +169,12 @@ public static function getChildGroupIds($groupIds) { */ public static function getParentGroupIds($groupIds) { if (!is_array($groupIds)) { - $groupIds = array($groupIds); + $groupIds = [$groupIds]; } $dao = new CRM_Contact_DAO_GroupNesting(); $query = "SELECT parent_group_id FROM civicrm_group_nesting WHERE child_group_id IN (" . implode(',', $groupIds) . ")"; $dao->query($query); - $parentGroupIds = array(); + $parentGroupIds = []; while ($dao->fetch()) { $parentGroupIds[] = $dao->parent_group_id; } @@ -172,13 +194,13 @@ public static function getParentGroupIds($groupIds) { */ public static function getDescendentGroupIds($groupIds, $includeSelf = TRUE) { if (!is_array($groupIds)) { - $groupIds = array($groupIds); + $groupIds = [$groupIds]; } $dao = new CRM_Contact_DAO_GroupNesting(); $query = "SELECT child_group_id, parent_group_id FROM civicrm_group_nesting WHERE parent_group_id IN (" . implode(',', $groupIds) . ")"; $dao->query($query); - $tmpGroupIds = array(); - $childGroupIds = array(); + $tmpGroupIds = []; + $childGroupIds = []; if ($includeSelf) { $childGroupIds = $groupIds; } diff --git a/CRM/Contact/BAO/GroupNestingCache.php b/CRM/Contact/BAO/GroupNestingCache.php index 2014ed146cbe..49efecdcef23 100644 --- a/CRM/Contact/BAO/GroupNestingCache.php +++ b/CRM/Contact/BAO/GroupNestingCache.php @@ -1,9 +1,9 @@ fetch()) { if (!array_key_exists($dao->child, $tree)) { - $tree[$dao->child] = array( - 'children' => array(), - 'parents' => array(), - ); + $tree[$dao->child] = [ + 'children' => [], + 'parents' => [], + ]; } if (!array_key_exists($dao->parent, $tree)) { - $tree[$dao->parent] = array( - 'children' => array(), - 'parents' => array(), - ); + $tree[$dao->parent] = [ + 'children' => [], + 'parents' => [], + ]; } $tree[$dao->child]['parents'][] = $dao->parent; @@ -84,7 +84,7 @@ static public function update() { "; CRM_Core_DAO::executeQuery($sql); - $values = array(); + $values = []; foreach (array_keys($tree) as $id) { $parents = implode(',', $tree[$id]['parents']); $children = implode(',', $tree[$id]['children']); @@ -100,7 +100,7 @@ static public function update() { } // this tree stuff is quite useful, so lets store it in the cache - CRM_Core_BAO_Cache::setItem($tree, 'contact groups', 'nestable tree hierarchy'); + Civi::cache('groups')->set('nestable tree hierarchy', $tree); } /** @@ -129,7 +129,7 @@ public static function checkCyclicGraph(&$tree) { * @return bool */ public static function isCyclic(&$tree, $id) { - $parents = $children = array(); + $parents = $children = []; self::getAll($parent, $tree, $id, 'parents'); self::getAll($child, $tree, $id, 'children'); @@ -153,11 +153,11 @@ public static function isCyclic(&$tree, $id) { * @return array */ public static function getPotentialCandidates($id, &$groups) { - $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); + $tree = Civi::cache('groups')->get('nestable tree hierarchy'); if ($tree === NULL) { self::update(); - $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); + $tree = Civi::cache('groups')->get('nestable tree hierarchy'); } $potential = $groups; @@ -219,11 +219,11 @@ public static function getAll(&$all, &$tree, $id, $token) { * @return string */ public static function json() { - $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); + $tree = Civi::cache('groups')->get('nestable tree hierarchy'); if ($tree === NULL) { self::update(); - $tree = CRM_Core_BAO_Cache::getItem('contact groups', 'nestable tree hierarchy'); + $tree = Civi::cache('groups')->get('nestable tree hierarchy'); } // get all the groups @@ -232,7 +232,7 @@ public static function json() { foreach ($groups as $id => $name) { $string = "id:'$id', name:'$name'"; if (isset($tree[$id])) { - $children = array(); + $children = []; if (!empty($tree[$id]['children'])) { foreach ($tree[$id]['children'] as $child) { $children[] = "{_reference:'$child'}"; diff --git a/CRM/Contact/BAO/GroupOrganization.php b/CRM/Contact/BAO/GroupOrganization.php index da6d27008a93..f57814da249c 100644 --- a/CRM/Contact/BAO/GroupOrganization.php +++ b/CRM/Contact/BAO/GroupOrganization.php @@ -1,9 +1,9 @@ copyValues($formattedValues); - // we have ensured we have group_id & organization_id so we can do a find knowing that - // this can only find a matching record - $groupOrganization->find(TRUE); + $groupOrganization->copyValues($params); + if (!isset($params['id'])) { + // we have ensured we have group_id & organization_id so we can do a find knowing that + // this can only find a matching record + $groupOrganization->find(TRUE); + } $groupOrganization->save(); return $groupOrganization; } - /** - * Format the params. - * - * @param array $params - * (reference ) an assoc array of name/value pairs. - * @param array $formatedValues - * (reference ) an assoc array of name/value pairs. - */ - public static function formatValues(&$params, &$formatedValues) { - if (!empty($params['group_organization'])) { - $formatedValues['id'] = $params['group_organization']; - } - - if (!empty($params['group_id'])) { - $formatedValues['group_id'] = $params['group_id']; - } - - if (!empty($params['organization_id'])) { - $formatedValues['organization_id'] = $params['organization_id']; - } - } - /** * Check if there is data to create the object. * diff --git a/CRM/Contact/BAO/Household.php b/CRM/Contact/BAO/Household.php index 62109f0b33ef..523849e72327 100644 --- a/CRM/Contact/BAO/Household.php +++ b/CRM/Contact/BAO/Household.php @@ -1,9 +1,9 @@ $dbName; @@ -105,7 +105,7 @@ public static function format(&$params, &$contact) { } } - foreach (array('prefix', 'suffix') as $name) { + foreach (['prefix', 'suffix'] as $name) { $dbName = "{$name}_id"; $value = $individual->$dbName; if ($value && !empty($params['preserveDBName'])) { @@ -122,7 +122,7 @@ public static function format(&$params, &$contact) { //2. lets get value from param if exists. //3. if not in params, lets get from db. - foreach (array('last', 'middle', 'first', 'nick') as $name) { + foreach (['last', 'middle', 'first', 'nick'] as $name) { $phpName = "{$name}Name"; $dbName = "{$name}_name"; $value = $individual->$dbName; @@ -139,7 +139,7 @@ public static function format(&$params, &$contact) { } } - foreach (array('prefix', 'suffix') as $name) { + foreach (['prefix', 'suffix'] as $name) { $dbName = "{$name}_id"; $value = $individual->$dbName; @@ -179,14 +179,14 @@ public static function format(&$params, &$contact) { } //first trim before further processing. - foreach (array('lastName', 'firstName', 'middleName') as $fld) { + foreach (['lastName', 'firstName', 'middleName'] as $fld) { $$fld = trim($$fld); } if ($lastName || $firstName || $middleName) { // make sure we have values for all the name fields. $formatted = $params; - $nameParams = array( + $nameParams = [ 'first_name' => $firstName, 'middle_name' => $middleName, 'last_name' => $lastName, @@ -196,7 +196,7 @@ public static function format(&$params, &$contact) { 'prefix_id' => $prefix_id, 'suffix_id' => $suffix_id, 'formal_title' => $formalTitle, - ); + ]; // make sure we have all the name fields. foreach ($nameParams as $name => $value) { if (empty($formatted[$name]) && $value) { @@ -204,9 +204,9 @@ public static function format(&$params, &$contact) { } } - $tokens = array(); + $tokens = []; CRM_Utils_Hook::tokens($tokens); - $tokenFields = array(); + $tokenFields = []; foreach ($tokens as $catTokens) { foreach ($catTokens as $token => $label) { $tokenFields[] = $token; @@ -216,14 +216,14 @@ public static function format(&$params, &$contact) { //build the sort name. $format = Civi::settings()->get('sort_name_format'); $sortName = CRM_Utils_Address::format($formatted, $format, - FALSE, FALSE, TRUE, $tokenFields + FALSE, FALSE, $tokenFields ); $sortName = trim($sortName); //build the display name. $format = Civi::settings()->get('display_name_format'); $displayName = CRM_Utils_Address::format($formatted, $format, - FALSE, FALSE, TRUE, $tokenFields + FALSE, FALSE, $tokenFields ); $displayName = trim($displayName); } @@ -248,7 +248,7 @@ public static function format(&$params, &$contact) { } //now set the names. - $names = array('displayName' => 'display_name', 'sortName' => 'sort_name'); + $names = ['displayName' => 'display_name', 'sortName' => 'sort_name']; foreach ($names as $value => $name) { if (empty($$value)) { if ($email) { @@ -273,29 +273,29 @@ public static function format(&$params, &$contact) { $format = CRM_Utils_Date::getDateFormat('birth'); if ($date = CRM_Utils_Array::value('birth_date', $params)) { - if (in_array($format, array( + if (in_array($format, [ 'dd-mm', 'mm/dd', - ))) { + ])) { $separator = '/'; if ($format == 'dd-mm') { $separator = '-'; } $date = $date . $separator . '1902'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'yy-mm', - ))) { + ])) { $date = $date . '-01'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'M yy', - ))) { + ])) { $date = $date . '-01'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'yy', - ))) { + ])) { $date = $date . '-01-01'; } $contact->birth_date = CRM_Utils_Date::processDate($date); @@ -305,29 +305,29 @@ public static function format(&$params, &$contact) { } if ($date = CRM_Utils_Array::value('deceased_date', $params)) { - if (in_array($format, array( + if (in_array($format, [ 'dd-mm', 'mm/dd', - ))) { + ])) { $separator = '/'; if ($format == 'dd-mm') { $separator = '-'; } $date = $date . $separator . '1902'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'yy-mm', - ))) { + ])) { $date = $date . '-01'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'M yy', - ))) { + ])) { $date = $date . '-01'; } - elseif (in_array($format, array( + elseif (in_array($format, [ 'yy', - ))) { + ])) { $date = $date . '-01-01'; } diff --git a/CRM/Contact/BAO/ProximityQuery.php b/CRM/Contact/BAO/ProximityQuery.php index 0418f5ab2ea4..c1e72783b872 100644 --- a/CRM/Contact/BAO/ProximityQuery.php +++ b/CRM/Contact/BAO/ProximityQuery.php @@ -1,9 +1,9 @@ = $minLatitude "; } @@ -264,7 +267,7 @@ public static function process(&$query, &$values) { list($name, $op, $distance, $grouping, $wildcard) = $values; // also get values array for all address related info - $proximityVars = array( + $proximityVars = [ 'street_address' => 1, 'city' => 1, 'postal_code' => 1, @@ -273,10 +276,12 @@ public static function process(&$query, &$values) { 'state_province' => 0, 'country' => 0, 'distance_unit' => 0, - ); + 'geo_code_1' => 0, + 'geo_code_2' => 0, + ]; - $proximityAddress = array(); - $qill = array(); + $proximityAddress = []; + $qill = []; foreach ($proximityVars as $var => $recordQill) { $proximityValues = $query->getWhereValues("prox_{$var}", $grouping); if (!empty($proximityValues) && @@ -327,21 +332,20 @@ public static function process(&$query, &$values) { } $qill = ts('Proximity search to a distance of %1 from %2', - array( + [ 1 => $qillUnits, 2 => implode(', ', $qill), - ) + ] ); - $fnName = isset($config->geocodeMethod) ? $config->geocodeMethod : NULL; - if (empty($fnName)) { - CRM_Core_Error::fatal(ts('Proximity searching requires you to set a valid geocoding provider')); - } - $query->_tables['civicrm_address'] = $query->_whereTables['civicrm_address'] = 1; - require_once str_replace('_', DIRECTORY_SEPARATOR, $fnName) . '.php'; - $fnName::format($proximityAddress); + if (empty($proximityAddress['geo_code_1']) || empty($proximityAddress['geo_code_2'])) { + if (!CRM_Core_BAO_Address::addGeocoderData($proximityAddress)) { + throw new CRM_Core_Exception(ts('Proximity searching requires you to set a valid geocoding provider')); + } + } + if ( !is_numeric(CRM_Utils_Array::value('geo_code_1', $proximityAddress)) || !is_numeric(CRM_Utils_Array::value('geo_code_2', $proximityAddress)) @@ -373,7 +377,7 @@ public static function fixInputParams(&$input) { foreach ($input as $param) { if (CRM_Utils_Array::value('0', $param) == 'prox_distance') { // add prox_ prefix to these - $param_alter = array('street_address', 'city', 'postal_code', 'state_province', 'country'); + $param_alter = ['street_address', 'city', 'postal_code', 'state_province', 'country']; foreach ($input as $key => $_param) { if (in_array($_param[0], $param_alter)) { diff --git a/CRM/Contact/BAO/Query.php b/CRM/Contact/BAO/Query.php index d519eeef4337..6f7cc442d644 100644 --- a/CRM/Contact/BAO/Query.php +++ b/CRM/Contact/BAO/Query.php @@ -1,9 +1,9 @@ 'gender', + 'prefix_id' => 'individual_prefix', + 'suffix_id' => 'individual_suffix', + 'communication_style_id' => 'communication_style', + ]; + /** * The cache to translate the option values into labels. * @@ -205,28 +239,28 @@ class CRM_Contact_BAO_Query { /** * Are we in search mode. * - * @var boolean + * @var bool */ public $_search = TRUE; /** * Should we skip permission checking. * - * @var boolean + * @var bool */ public $_skipPermission = FALSE; /** * Should we skip adding of delete clause. * - * @var boolean + * @var bool */ public $_skipDeleteClause = FALSE; /** * Are we in strict mode (use equality over LIKE) * - * @var boolean + * @var bool */ public $_strict = FALSE; @@ -242,21 +276,21 @@ class CRM_Contact_BAO_Query { /** * Should we only search on primary location. * - * @var boolean + * @var bool */ public $_primaryLocation = TRUE; /** * Are contact ids part of the query. * - * @var boolean + * @var bool */ public $_includeContactIds = FALSE; /** * Should we use the smart group cache. * - * @var boolean + * @var bool */ public $_smartGroupCache = TRUE; @@ -278,12 +312,13 @@ class CRM_Contact_BAO_Query { * Should we enable the distinct clause, used if we are including * more than one group * - * @var boolean + * @var bool */ public $_useDistinct = FALSE; /** * Should we just display one contact record + * @var bool */ public $_useGroupBy = FALSE; @@ -292,14 +327,14 @@ class CRM_Contact_BAO_Query { * * @var array */ - static $_relType; + public static $_relType; /** * The activity role * * @var array */ - static $_activityRole; + public static $_activityRole; /** * Consider the component activity type @@ -307,7 +342,7 @@ class CRM_Contact_BAO_Query { * * @var array */ - static $_considerCompActivities; + public static $_considerCompActivities; /** * Consider with contact activities only, @@ -315,7 +350,7 @@ class CRM_Contact_BAO_Query { * * @var array */ - static $_withContactActivitiesOnly; + public static $_withContactActivitiesOnly; /** * Use distinct component clause for component searches @@ -338,31 +373,32 @@ class CRM_Contact_BAO_Query { * * @var array */ - public static $_openedPanes = array(); + public static $_openedPanes = []; /** * For search builder - which custom fields are location-dependent * @var array */ - public $_locationSpecificCustomFields = array(); + public $_locationSpecificCustomFields = []; /** * The tables which have a dependency on location and/or address * * @var array */ - static $_dependencies = array( + public static $_dependencies = [ 'civicrm_state_province' => 1, 'civicrm_country' => 1, 'civicrm_county' => 1, 'civicrm_address' => 1, 'civicrm_location_type' => 1, - ); + ]; /** * List of location specific fields. + * @var array */ - static $_locationSpecificFields = array( + public static $_locationSpecificFields = [ 'street_address', 'street_number', 'street_name', @@ -383,26 +419,32 @@ class CRM_Contact_BAO_Query { 'im', 'address_name', 'master_id', - ); + ]; /** * Remember if we handle either end of a number or date range * so we can skip the other + * @var array */ - protected $_rangeCache = array(); + protected $_rangeCache = []; /** - * Set to true when $this->relationship is run to avoid adding twice - * @var Boolean + * Set to true when $this->relationship is run to avoid adding twice. + * + * @var bool */ protected $_relationshipValuesAdded = FALSE; /** - * Set to the name of the temp table if one has been created - * @var String + * Set to the name of the temp table if one has been created. + * + * @var string */ - static $_relationshipTempTable = NULL; + public static $_relationshipTempTable = NULL; + + public $_pseudoConstantsSelect = []; - public $_pseudoConstantsSelect = array(); + public $_groupUniqueKey = NULL; + public $_groupKeys = []; /** * Class constructor which also does all the work. @@ -420,8 +462,7 @@ class CRM_Contact_BAO_Query { * @param null $displayRelationshipType * @param string $operator * @param string $apiEntity - * - * @return \CRM_Contact_BAO_Query + * @param bool|null $primaryLocationOnly */ public function __construct( $params = NULL, $returnProperties = NULL, $fields = NULL, @@ -429,15 +470,20 @@ public function __construct( $skipPermission = FALSE, $searchDescendentGroups = TRUE, $smartGroupCache = TRUE, $displayRelationshipType = NULL, $operator = 'AND', - $apiEntity = NULL + $apiEntity = NULL, + $primaryLocationOnly = NULL ) { + if ($primaryLocationOnly === NULL) { + $primaryLocationOnly = Civi::settings()->get('searchPrimaryDetailsOnly'); + } + $this->_primaryLocation = $primaryLocationOnly; $this->_params = &$params; if ($this->_params == NULL) { - $this->_params = array(); + $this->_params = []; } if ($returnProperties === self::NO_RETURN_PROPERTIES) { - $this->_returnProperties = array(); + $this->_returnProperties = []; } elseif (empty($returnProperties)) { $this->_returnProperties = self::defaultReturnProperties($mode); @@ -461,6 +507,10 @@ public function __construct( } else { $this->_fields = CRM_Contact_BAO_Contact::exportableFields('All', FALSE, TRUE, TRUE, FALSE, !$skipPermission); + // The legacy hacked fields will output as a string rather than their underlying type. + foreach (array_keys($this->legacyHackedFields) as $fieldName) { + $this->_fields[$fieldName]['type'] = CRM_Utils_Type::T_STRING; + } $fields = CRM_Core_Component::getQueryFields(!$this->_skipPermission); unset($fields['note']); @@ -487,16 +537,16 @@ public function __construct( * This sort-of duplicates $mode in a confusing way. Probably not by design. */ public function initialize($apiEntity = NULL) { - $this->_select = array(); - $this->_element = array(); - $this->_tables = array(); - $this->_whereTables = array(); - $this->_where = array(); - $this->_qill = array(); - $this->_options = array(); - $this->_cfIDs = array(); - $this->_paramLookup = array(); - $this->_having = array(); + $this->_select = []; + $this->_element = []; + $this->_tables = []; + $this->_whereTables = []; + $this->_where = []; + $this->_qill = []; + $this->_options = []; + $this->_cfIDs = []; + $this->_paramLookup = []; + $this->_having = []; $this->_customQuery = NULL; @@ -516,14 +566,16 @@ public function initialize($apiEntity = NULL) { $this->_whereTables = $this->_tables; $this->selectClause($apiEntity); - $this->_whereClause = $this->whereClause($apiEntity); + $isForcePrimaryOnly = !empty($apiEntity); + $this->_whereClause = $this->whereClause($isForcePrimaryOnly); if (array_key_exists('civicrm_contribution', $this->_whereTables)) { $component = 'contribution'; } if (array_key_exists('civicrm_membership', $this->_whereTables)) { $component = 'membership'; } - if (isset($component)) { + if (isset($component) && !$this->_skipPermission) { + // Unit test coverage in api_v3_FinancialTypeACLTest::testGetACLContribution. CRM_Financial_BAO_FinancialType::buildPermissionedClause($this->_whereClause, $component); } @@ -552,7 +604,7 @@ public function initialize($apiEntity = NULL) { */ public function buildParamsLookup() { $trashParamExists = FALSE; - $paramByGroup = array(); + $paramByGroup = []; foreach ($this->_params as $k => $param) { if (!empty($param[0]) && $param[0] == 'contact_is_deleted') { $trashParamExists = TRUE; @@ -568,16 +620,16 @@ public function buildParamsLookup() { //cycle through group sets and explicitly add trash param if not set foreach ($paramByGroup as $setID => $set) { if ( - !in_array(array('contact_is_deleted', '=', '1', $setID, '0'), $this->_params) && - !in_array(array('contact_is_deleted', '=', '0', $setID, '0'), $this->_params) + !in_array(['contact_is_deleted', '=', '1', $setID, '0'], $this->_params) && + !in_array(['contact_is_deleted', '=', '0', $setID, '0'], $this->_params) ) { - $this->_params[] = array( + $this->_params[] = [ 'contact_is_deleted', '=', '0', $setID, '0', - ); + ]; } } } @@ -586,10 +638,10 @@ public function buildParamsLookup() { if (empty($value[0])) { continue; } - $cfID = CRM_Core_BAO_CustomField::getKeyID($value[0]); + $cfID = CRM_Core_BAO_CustomField::getKeyID(str_replace('_relative', '', $value[0])); if ($cfID) { if (!array_key_exists($cfID, $this->_cfIDs)) { - $this->_cfIDs[$cfID] = array(); + $this->_cfIDs[$cfID] = []; } // Set wildcard value based on "and/or" selection foreach ($this->_params as $key => $param) { @@ -602,7 +654,7 @@ public function buildParamsLookup() { } if (!array_key_exists($value[0], $this->_paramLookup)) { - $this->_paramLookup[$value[0]] = array(); + $this->_paramLookup[$value[0]] = []; } if ($value[0] !== 'group') { // Just trying to unravel how group interacts here! This whole function is weird. @@ -619,7 +671,7 @@ public function buildParamsLookup() { * This sort-of duplicates $mode in a confusing way. Probably not by design. */ public function addSpecialFields($apiEntity) { - static $special = array('contact_type', 'contact_sub_type', 'sort_name', 'display_name'); + static $special = ['contact_type', 'contact_sub_type', 'sort_name', 'display_name']; // if get called via Contact.get API having address_id as return parameter if ($apiEntity == 'Contact') { $special[] = 'address_id'; @@ -677,7 +729,6 @@ public function selectClause($apiEntity = NULL) { ($name == 'parent_id') ) { CRM_Activity_BAO_Query::select($this); - continue; } // if this is a hierarchical name, we ignore it @@ -690,7 +741,7 @@ public function selectClause($apiEntity = NULL) { $makeException = FALSE; //special handling for groups/tags - if (in_array($name, array('groups', 'tags', 'notes')) + if (in_array($name, ['groups', 'tags', 'notes']) && isset($this->_returnProperties[substr($name, 0, -1)]) ) { // @todo instead of setting make exception to get us into @@ -702,7 +753,7 @@ public function selectClause($apiEntity = NULL) { // since note has 3 different options we need special handling // note / note_subject / note_body if ($name == 'notes') { - foreach (array('note', 'note_subject', 'note_body') as $noteField) { + foreach (['note', 'note_subject', 'note_body'] as $noteField) { if (isset($this->_returnProperties[$noteField])) { $makeException = TRUE; break; @@ -714,13 +765,13 @@ public function selectClause($apiEntity = NULL) { if ( !empty($this->_paramLookup[$name]) || !empty($this->_returnProperties[$name]) - || $this->pseudoConstantNameIsInReturnProperties($field) + || $this->pseudoConstantNameIsInReturnProperties($field, $name) || $makeException ) { if ($cfID) { // add to cfIDs array if not present if (!array_key_exists($cfID, $this->_cfIDs)) { - $this->_cfIDs[$cfID] = array(); + $this->_cfIDs[$cfID] = []; } } elseif (isset($field['where'])) { @@ -740,10 +791,10 @@ public function selectClause($apiEntity = NULL) { } if (in_array($tableName, - array('email_greeting', 'postal_greeting', 'addressee'))) { + ['email_greeting', 'postal_greeting', 'addressee'])) { $this->_element["{$name}_id"] = 1; $this->_select["{$name}_id"] = "contact_a.{$name}_id as {$name}_id"; - $this->_pseudoConstantsSelect[$name] = array('pseudoField' => $tableName, 'idCol' => "{$name}_id"); + $this->_pseudoConstantsSelect[$name] = ['pseudoField' => $tableName, 'idCol' => "{$name}_id"]; $this->_pseudoConstantsSelect[$name]['select'] = "{$name}.{$fieldName} as $name"; $this->_pseudoConstantsSelect[$name]['element'] = $name; @@ -781,37 +832,37 @@ public function selectClause($apiEntity = NULL) { } } else { - if (!in_array($tableName, array('civicrm_state_province', 'civicrm_country', 'civicrm_county'))) { + if (!in_array($tableName, ['civicrm_state_province', 'civicrm_country', 'civicrm_county'])) { $this->_tables[$tableName] = 1; } // also get the id of the tableName $tName = substr($tableName, 8); - if (in_array($tName, array('country', 'state_province', 'county'))) { + if (in_array($tName, ['country', 'state_province', 'county'])) { if ($tName == 'state_province') { - $this->_pseudoConstantsSelect['state_province_name'] = array( + $this->_pseudoConstantsSelect['state_province_name'] = [ 'pseudoField' => "{$tName}", 'idCol' => "{$tName}_id", 'bao' => 'CRM_Core_BAO_Address', 'table' => "civicrm_{$tName}", 'join' => " LEFT JOIN civicrm_{$tName} ON civicrm_address.{$tName}_id = civicrm_{$tName}.id ", - ); + ]; - $this->_pseudoConstantsSelect[$tName] = array( + $this->_pseudoConstantsSelect[$tName] = [ 'pseudoField' => 'state_province_abbreviation', 'idCol' => "{$tName}_id", 'table' => "civicrm_{$tName}", 'join' => " LEFT JOIN civicrm_{$tName} ON civicrm_address.{$tName}_id = civicrm_{$tName}.id ", - ); + ]; } else { - $this->_pseudoConstantsSelect[$name] = array( + $this->_pseudoConstantsSelect[$name] = [ 'pseudoField' => "{$tName}_id", 'idCol' => "{$tName}_id", 'bao' => 'CRM_Core_BAO_Address', 'table' => "civicrm_{$tName}", 'join' => " LEFT JOIN civicrm_{$tName} ON civicrm_address.{$tName}_id = civicrm_{$tName}.id ", - ); + ]; } $this->_select["{$tName}_id"] = "civicrm_address.{$tName}_id as {$tName}_id"; @@ -842,7 +893,7 @@ public function selectClause($apiEntity = NULL) { elseif ($tName == 'contact' && $fieldName === 'id') { // Handled elsewhere, explicitly ignore. Possibly for all tables... } - elseif (in_array($tName, array('country', 'county'))) { + elseif (in_array($tName, ['country', 'county'])) { $this->_pseudoConstantsSelect[$name]['select'] = "{$field['where']} as `$name`"; $this->_pseudoConstantsSelect[$name]['element'] = $name; } @@ -855,25 +906,13 @@ public function selectClause($apiEntity = NULL) { $this->_select[$name] = "{$field['where']} as `$name`"; } } + elseif ($this->pseudoConstantNameIsInReturnProperties($field, $name)) { + $this->addPseudoconstantFieldToSelect($name); + } else { - // If we have an option group defined then rather than joining the option value table in - // (which is an unindexed join) we render the option value on output. - // @todo - extend this to other pseudoconstants. - if ($this->pseudoConstantNameIsInReturnProperties($field)) { - $pseudoFieldName = $field['pseudoconstant']['optionGroupName']; - $this->_pseudoConstantsSelect[$pseudoFieldName] = array( - 'pseudoField' => $field['name'], - 'idCol' => $field['name'], - 'field_name' => $field['name'], - 'bao' => $field['bao'], - 'pseudoconstant' => $field['pseudoconstant'], - ); - $this->_tables[$tableName] = 1; - $this->_element[$pseudoFieldName] = 1; - } $this->_select[$name] = str_replace('civicrm_contact.', 'contact_a.', "{$field['where']} as `$name`"); } - if (!in_array($tName, array('state_province', 'country', 'county'))) { + if (!in_array($tName, ['state_province', 'country', 'county'])) { $this->_element[$name] = 1; } } @@ -900,10 +939,10 @@ public function selectClause($apiEntity = NULL) { $this->_element[$name] = 1; $this->_tables['civicrm_group_contact'] = 1; $this->_tables['civicrm_group_contact_cache'] = 1; - $this->_pseudoConstantsSelect["{$name}"] = array( + $this->_pseudoConstantsSelect["{$name}"] = [ 'pseudoField' => "groups", 'idCol' => "groups", - ); + ]; } elseif ($name === 'notes') { //@todo move this handling outside the big IF & ditch $makeException @@ -928,7 +967,7 @@ public function selectClause($apiEntity = NULL) { // this is a custom field with range search enabled, so we better check for two/from values if (!empty($this->_paramLookup[$name . '_from'])) { if (!array_key_exists($cfID, $this->_cfIDs)) { - $this->_cfIDs[$cfID] = array(); + $this->_cfIDs[$cfID] = []; } foreach ($this->_paramLookup[$name . '_from'] as $pID => $p) { // search in the cdID array for the same grouping @@ -940,14 +979,14 @@ public function selectClause($apiEntity = NULL) { } } if (!$fnd) { - $p[2] = array('from' => $p[2]); + $p[2] = ['from' => $p[2]]; $this->_cfIDs[$cfID][] = $p; } } } if (!empty($this->_paramLookup[$name . '_to'])) { if (!array_key_exists($cfID, $this->_cfIDs)) { - $this->_cfIDs[$cfID] = array(); + $this->_cfIDs[$cfID] = []; } foreach ($this->_paramLookup[$name . '_to'] as $pID => $p) { // search in the cdID array for the same grouping @@ -959,7 +998,7 @@ public function selectClause($apiEntity = NULL) { } } if (!$fnd) { - $p[2] = array('to' => $p[2]); + $p[2] = ['to' => $p[2]]; $this->_cfIDs[$cfID][] = $p; } } @@ -1002,18 +1041,18 @@ public function addHierarchicalElements() { return; } - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $processed = array(); + $locationTypes = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); + $processed = []; $index = 0; $addressCustomFields = CRM_Core_BAO_CustomField::getFieldsForImport('Address'); - $addressCustomFieldIds = array(); + $addressCustomFieldIds = []; foreach ($this->_returnProperties['location'] as $name => $elements) { $lCond = self::getPrimaryCondition($name); + $locationTypeId = is_numeric($name) ? NULL : array_search($name, $locationTypes); if (!$lCond) { - $locationTypeId = array_search($name, $locationTypes); if ($locationTypeId === FALSE) { continue; } @@ -1025,7 +1064,6 @@ public function addHierarchicalElements() { } $name = str_replace(' ', '_', $name); - $tName = "$name-location_type"; $ltName = "`$name-location_type`"; $this->_select["{$tName}_id"] = "`$tName`.id as `{$tName}_id`"; @@ -1034,9 +1072,8 @@ public function addHierarchicalElements() { $this->_element["{$tName}"] = 1; $locationTypeName = $tName; - $locationTypeJoin = array(); + $locationTypeJoin = []; - $addAddress = FALSE; $addWhereCount = 0; foreach ($elements as $elementFullName => $dontCare) { $index++; @@ -1051,20 +1088,14 @@ public function addHierarchicalElements() { $addressCustomFieldIds[$cfID][$name] = 1; } } - //add address table only once + // add address table - doesn't matter if we do it mutliple times - it's the same data + // @todo ditch the double processing of addressJoin if ((in_array($elementCmpName, self::$_locationSpecificFields) || !empty($addressCustomFieldIds)) - && !$addAddress - && !in_array($elementCmpName, array('email', 'phone', 'im', 'openid')) + && !in_array($elementCmpName, ['email', 'phone', 'im', 'openid']) ) { - $tName = "$name-address"; - $aName = "`$name-address`"; - $this->_select["{$tName}_id"] = "`$tName`.id as `{$tName}_id`"; - $this->_element["{$tName}_id"] = 1; - $addressJoin = "\nLEFT JOIN civicrm_address $aName ON ($aName.contact_id = contact_a.id AND $aName.$lCond)"; - $this->_tables[$tName] = $addressJoin; + list($aName, $addressJoin) = $this->addAddressTable($name, $lCond); $locationTypeJoin[$tName] = " ( $aName.location_type_id = $ltName.id ) "; $processed[$aName] = 1; - $addAddress = TRUE; } $cond = $elementType = ''; @@ -1105,7 +1136,7 @@ public function addHierarchicalElements() { } elseif (is_numeric($name)) { //this for phone type to work - if (in_array($elementName, array('phone', 'phone_ext'))) { + if (in_array($elementName, ['phone', 'phone_ext'])) { $field = CRM_Utils_Array::value($elementName . "-Primary" . $elementType, $this->_fields); } else { @@ -1114,7 +1145,7 @@ public function addHierarchicalElements() { } else { //this is for phone type to work for profile edit - if (in_array($elementName, array('phone', 'phone_ext'))) { + if (in_array($elementName, ['phone', 'phone_ext'])) { $field = CRM_Utils_Array::value($elementName . "-$locationTypeId$elementType", $this->_fields); } else { @@ -1136,7 +1167,7 @@ public function addHierarchicalElements() { foreach ($this->_params as $id => $values) { if ((is_array($values) && $values[0] == $nm) || - (in_array($elementName, array('phone', 'im')) + (in_array($elementName, ['phone', 'im']) && (strpos($values[0], $nm) !== FALSE) ) ) { @@ -1165,18 +1196,18 @@ public function addHierarchicalElements() { $a = Civi::settings()->get('address_format'); if (substr_count($a, 'state_province_name') > 0) { - $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = array( + $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = [ 'pseudoField' => "{$pf}_id", 'idCol' => "{$tName}_id", 'bao' => 'CRM_Core_BAO_Address', - ); + ]; $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"]['select'] = "`$tName`.name as `{$name}-{$elementFullName}`"; } else { - $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = array( + $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = [ 'pseudoField' => 'state_province_abbreviation', 'idCol' => "{$tName}_id", - ); + ]; $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"]['select'] = "`$tName`.abbreviation as `{$name}-{$elementFullName}`"; } } @@ -1187,11 +1218,11 @@ public function addHierarchicalElements() { $this->_element[$provider] = 1; } if ($pf == 'country' || $pf == 'county') { - $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = array( + $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"] = [ 'pseudoField' => "{$pf}_id", 'idCol' => "{$tName}_id", 'bao' => 'CRM_Core_BAO_Address', - ); + ]; $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"]['select'] = "`$tName`.$fieldName as `{$name}-{$elementFullName}`"; } else { @@ -1199,7 +1230,7 @@ public function addHierarchicalElements() { } } - if (in_array($pf, array('state_province', 'country', 'county'))) { + if (in_array($pf, ['state_province', 'country', 'county'])) { $this->_pseudoConstantsSelect["{$name}-{$elementFullName}"]['element'] = "{$name}-{$elementFullName}"; } else { @@ -1289,7 +1320,7 @@ public function addHierarchicalElements() { // table should be present in $this->_whereTables, // to add its condition in location type join, CRM-3939. if ($addWhereCount) { - $locClause = array(); + $locClause = []; foreach ($this->_whereTables as $tableName => $clause) { if (!empty($locationTypeJoin[$tableName])) { $locClause[] = $locationTypeJoin[$tableName]; @@ -1306,7 +1337,7 @@ public function addHierarchicalElements() { $customQuery = new CRM_Core_BAO_CustomQuery($addressCustomFieldIds); foreach ($addressCustomFieldIds as $cfID => $locTypeName) { foreach ($locTypeName as $name => $dnc) { - $this->_locationSpecificCustomFields[$cfID] = array($name, array_search($name, $locationTypes)); + $this->_locationSpecificCustomFields[$cfID] = [$name, array_search($name, $locationTypes)]; $fieldName = "$name-custom_{$cfID}"; $tName = "$name-address-custom-{$cfID}"; $aName = "`$name-address-custom-{$cfID}`"; @@ -1381,7 +1412,9 @@ public function query($count = FALSE, $sortByChar = FALSE, $groupContacts = FALS } } elseif ($sortByChar) { - $select = 'SELECT DISTINCT UPPER(LEFT(contact_a.sort_name, 1)) as sort_name'; + // @fixme add the deprecated warning back in (it breaks CRM_Contact_SelectorTest::testSelectorQuery) + // CRM_Core_Error::deprecatedFunctionWarning('sort by char is deprecated - use alphabetQuery method'); + $select = 'SELECT DISTINCT LEFT(contact_a.sort_name, 1) as sort_name'; $from = $this->_simpleFromClause; } elseif ($groupContacts) { @@ -1416,13 +1449,13 @@ public function query($count = FALSE, $sortByChar = FALSE, $groupContacts = FALS $group->find(TRUE); if (!isset($group->saved_search_id)) { - $tbName = "`civicrm_group_contact-{$groupId}`"; + $tbName = "civicrm_group_contact"; // CRM-17254 don't retrieve extra fields if contact_id is specifically requested // as this will add load to an intentionally light query. // ideally this code would be removed as it appears to be to support CRM-1203 // and passing in the required returnProperties from the url would // make more sense that globally applying the requirements of one form. - if (($this->_returnProperties != array('contact_id'))) { + if (($this->_returnProperties != ['contact_id'])) { $this->_select['group_contact_id'] = "$tbName.id as group_contact_id"; $this->_element['group_contact_id'] = 1; $this->_select['status'] = "$tbName.status as status"; @@ -1441,11 +1474,7 @@ public function query($count = FALSE, $sortByChar = FALSE, $groupContacts = FALS } } - $select = "SELECT "; - if (isset($this->_distinctComponentClause)) { - $select .= "{$this->_distinctComponentClause}, "; - } - $select .= implode(', ', $this->_select); + $select = $this->getSelect(); $from = $this->_fromClause; } @@ -1479,7 +1508,7 @@ public function query($count = FALSE, $sortByChar = FALSE, $groupContacts = FALS $this->filterRelatedContacts($from, $where, $having); } - return array($select, $from, $where, $having); + return [$select, $from, $where, $having]; } /** @@ -1548,19 +1577,59 @@ public static function fixDateValues($relative, &$from, &$to) { * @return array */ public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals = FALSE, $apiEntity = NULL, - $entityReferenceFields = array()) { - $params = array(); + $entityReferenceFields = []) { + $params = []; if (empty($formValues)) { return $params; } self::filterCountryFromValuesIfStateExists($formValues); + // We shouldn't have to whitelist fields to not hack but here we are, for now. + $nonLegacyDateFields = [ + 'participant_register_date_relative', + 'receive_date_relative', + 'pledge_end_date_relative', + 'pledge_create_date_relative', + 'pledge_start_date_relative', + 'pledge_payment_scheduled_date_relative', + 'membership_join_date_relative', + 'membership_start_date_relative', + 'membership_end_date_relative', + ]; + // Handle relative dates first + foreach (array_keys($formValues) as $id) { + if ( + !in_array($id, $nonLegacyDateFields) && ( + preg_match('/_date_relative$/', $id) || + $id == 'event_relative' || + $id == 'case_from_relative' || + $id == 'case_to_relative') + ) { + if ($id == 'event_relative') { + $fromRange = 'event_start_date_low'; + $toRange = 'event_end_date_high'; + } + elseif ($id == 'case_from_relative') { + $fromRange = 'case_from_start_date_low'; + $toRange = 'case_from_start_date_high'; + } + elseif ($id == 'case_to_relative') { + $fromRange = 'case_to_end_date_low'; + $toRange = 'case_to_end_date_high'; + } + else { + $dateComponent = explode('_date_relative', $id); + $fromRange = "{$dateComponent[0]}_date_low"; + $toRange = "{$dateComponent[0]}_date_high"; + } - foreach ($formValues as $id => &$val) { - // CRM-19374 - we don't want to change $val in $formValues. - // Assign it to a temp variable which operates while iteration. - $values = $val; + if (array_key_exists($fromRange, $formValues) && array_key_exists($toRange, $formValues)) { + CRM_Contact_BAO_Query::fixDateValues($formValues[$id], $formValues[$fromRange], $formValues[$toRange]); + } + } + } + foreach ($formValues as $id => $values) { if (self::isAlreadyProcessedForQueryFormat($values)) { $params[] = $values; continue; @@ -1570,19 +1639,19 @@ public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals // The form uses 1 field to represent two db fields if ($id == 'contact_type' && $values && (!is_array($values) || !array_intersect(array_keys($values), CRM_Core_DAO::acceptedSQLOperators()))) { - $contactType = array(); - $subType = array(); + $contactType = []; + $subType = []; foreach ((array) $values as $key => $type) { - $types = explode('__', is_numeric($type) ? $key : $type); + $types = explode('__', is_numeric($type) ? $key : $type, 2); $contactType[$types[0]] = $types[0]; // Add sub-type if specified if (!empty($types[1])) { $subType[$types[1]] = $types[1]; } } - $params[] = array('contact_type', 'IN', $contactType, 0, 0); + $params[] = ['contact_type', 'IN', $contactType, 0, 0]; if ($subType) { - $params[] = array('contact_sub_type', 'IN', $subType, 0, 0); + $params[] = ['contact_sub_type', 'IN', $subType, 0, 0]; } } elseif ($id == 'privacy') { @@ -1590,68 +1659,45 @@ public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals $op = !empty($formValues['privacy']['do_not_toggle']) ? '=' : '!='; foreach ($formValues['privacy'] as $key => $value) { if ($value) { - $params[] = array($key, $op, $value, 0, 0); + $params[] = [$key, $op, $value, 0, 0]; } } } } elseif ($id == 'email_on_hold') { - if ($formValues['email_on_hold']['on_hold']) { - $params[] = array('on_hold', '=', $formValues['email_on_hold']['on_hold'], 0, 0); + if ($onHoldValue = CRM_Utils_Array::value('email_on_hold', $formValues)) { + // onHoldValue should be 0 or 1 or an array. Some legacy groups may hold '' + // so in 5.11 we have an extra if that should become redundant over time. + // https://lab.civicrm.org/dev/core/issues/745 + // @todo this renaming of email_on_hold to on_hold needs revisiting + // it preceeds recent changes but causes the default not to reload. + $onHoldValue = array_filter((array) $onHoldValue, 'is_numeric'); + if (!empty($onHoldValue)) { + $params[] = ['on_hold', 'IN', $onHoldValue, 0, 0]; + } } } elseif (substr($id, 0, 7) == 'custom_' && ( - substr($id, -9, 9) == '_relative' - || substr($id, -5, 5) == '_from' + substr($id, -5, 5) == '_from' || substr($id, -3, 3) == '_to' ) ) { self::convertCustomRelativeFields($formValues, $params, $values, $id); } - elseif (preg_match('/_date_relative$/', $id) || - $id == 'event_relative' || - $id == 'case_from_relative' || - $id == 'case_to_relative' || - $id == 'participant_relative' + elseif ( + !in_array($id, $nonLegacyDateFields) && ( + preg_match('/_date_relative$/', $id) || + $id == 'event_relative' || + $id == 'case_from_relative' || + $id == 'case_to_relative') ) { - if ($id == 'event_relative') { - $fromRange = 'event_start_date_low'; - $toRange = 'event_end_date_high'; - } - elseif ($id == 'participant_relative') { - $fromRange = 'participant_register_date_low'; - $toRange = 'participant_register_date_high'; - } - elseif ($id == 'case_from_relative') { - $fromRange = 'case_from_start_date_low'; - $toRange = 'case_from_start_date_high'; - } - elseif ($id == 'case_to_relative') { - $fromRange = 'case_to_end_date_low'; - $toRange = 'case_to_end_date_high'; - } - else { - $dateComponent = explode('_date_relative', $id); - $fromRange = "{$dateComponent[0]}_date_low"; - $toRange = "{$dateComponent[0]}_date_high"; - } - - if (array_key_exists($fromRange, $formValues) && array_key_exists($toRange, $formValues)) { - // relative dates are not processed correctly as lower date value were ignored, - // to ensure both high and low date value got added IF there is relative date, - // we need to reset $formValues by unset and then adding again via CRM_Contact_BAO_Query::fixDateValues(...) - if (!empty($formValues[$id])) { - unset($formValues[$fromRange]); - unset($formValues[$toRange]); - } - CRM_Contact_BAO_Query::fixDateValues($formValues[$id], $formValues[$fromRange], $formValues[$toRange]); - continue; - } + // Already handled in previous loop + continue; } elseif (in_array($id, $entityReferenceFields) && !empty($values) && is_string($values) && (strpos($values, ',') != FALSE)) { - $params[] = array($id, 'IN', explode(',', $values), 0, 0); + $params[] = [$id, 'IN', explode(',', $values), 0, 0]; } else { $values = CRM_Contact_BAO_Query::fixWhereValues($id, $values, $wildcard, $useEquals, $apiEntity); @@ -1673,14 +1719,14 @@ public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals * */ public static function legacyConvertFormValues($id, &$values) { - $legacyElements = array( + $legacyElements = [ 'group', 'tag', 'contact_tags', 'contact_type', 'membership_type_id', 'membership_status_id', - ); + ]; if (in_array($id, $legacyElements) && is_array($values)) { // prior to 4.7, formValues for some attributes (e.g. group, tag) are stored in array(id1 => 1, id2 => 1), // as per the recent Search fixes $values need to be in standard array(id1, id2) format @@ -1715,7 +1761,7 @@ public static function fixWhereValues($id, &$values, $wildcard = 0, $useEquals = } if (!$skipWhere) { - $skipWhere = array( + $skipWhere = [ 'task', 'radio_ts', 'uf_group_id', @@ -1723,7 +1769,7 @@ public static function fixWhereValues($id, &$values, $wildcard = 0, $useEquals = 'qfKey', 'operator', 'display_relationship_type', - ); + ]; } if (in_array($id, $skipWhere) || @@ -1742,7 +1788,7 @@ public static function fixWhereValues($id, &$values, $wildcard = 0, $useEquals = } if (!$likeNames) { - $likeNames = array('sort_name', 'email', 'note', 'display_name'); + $likeNames = ['sort_name', 'email', 'note', 'display_name']; } // email comes in via advanced search @@ -1752,18 +1798,18 @@ public static function fixWhereValues($id, &$values, $wildcard = 0, $useEquals = } if (!$useEquals && in_array($id, $likeNames)) { - $result = array($id, 'LIKE', $values, 0, 1); + $result = [$id, 'LIKE', $values, 0, 1]; } elseif (is_string($values) && strpos($values, '%') !== FALSE) { - $result = array($id, 'LIKE', $values, 0, 0); + $result = [$id, 'LIKE', $values, 0, 0]; } elseif ($id == 'contact_type' || (!empty($values) && is_array($values) && !in_array(key($values), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) ) { - $result = array($id, 'IN', $values, 0, $wildcard); + $result = [$id, 'IN', $values, 0, $wildcard]; } else { - $result = array($id, '=', $values, 0, $wildcard); + $result = [$id, '=', $values, 0, $wildcard]; } return $result; @@ -1773,9 +1819,17 @@ public static function fixWhereValues($id, &$values, $wildcard = 0, $useEquals = * Get the where clause for a single field. * * @param array $values - * @param string $apiEntity + * @param bool $isForcePrimaryOnly + * + * @throws \CRM_Core_Exception */ - public function whereClauseSingle(&$values, $apiEntity = NULL) { + public function whereClauseSingle(&$values, $isForcePrimaryOnly = FALSE) { + if ($this->isARelativeDateField($values[0])) { + $this->buildRelativeDateQuery($values); + return; + } + // @todo also handle _low, _high generically here with if ($query->buildDateRangeQuery($values)) {return} + // do not process custom fields or prefixed contact ids or component params if (CRM_Core_BAO_CustomField::getKeyID($values[0]) || (substr($values[0], 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) || @@ -1846,7 +1900,7 @@ public function whereClauseSingle(&$values, $apiEntity = NULL) { case 'email': case 'email_id': - $this->email($values, $apiEntity); + $this->email($values, $isForcePrimaryOnly); return; case 'phone_numeric': @@ -1898,6 +1952,8 @@ public function whereClauseSingle(&$values, $apiEntity = NULL) { case 'activity_date': case 'activity_date_low': case 'activity_date_high': + case 'activity_date_time_low': + case 'activity_date_time_high': case 'activity_role': case 'activity_status_id': case 'activity_status': @@ -1972,8 +2028,11 @@ public function whereClauseSingle(&$values, $apiEntity = NULL) { case 'relation_start_date_low': case 'relation_end_date_high': case 'relation_end_date_low': + case 'relation_active_period_date_high': + case 'relation_active_period_date_low': case 'relation_target_name': case 'relation_status': + case 'relation_description': case 'relation_date_low': case 'relation_date_high': $this->relationship($values); @@ -1997,6 +2056,8 @@ public function whereClauseSingle(&$values, $apiEntity = NULL) { case 'prox_postal_code': case 'prox_state_province_id': case 'prox_country_id': + case 'prox_geo_code_1': + case 'prox_geo_code_2': // handled by the proximity_distance clause return; @@ -2009,13 +2070,14 @@ public function whereClauseSingle(&$values, $apiEntity = NULL) { /** * Given a list of conditions in params generate the required where clause. * - * @param string $apiEntity + * @param bool $isForcePrimaryEmailOnly * * @return string + * @throws \CRM_Core_Exception */ - public function whereClause($apiEntity = NULL) { - $this->_where[0] = array(); - $this->_qill[0] = array(); + public function whereClause($isForcePrimaryEmailOnly = NULL) { + $this->_where[0] = []; + $this->_qill[0] = []; $this->includeContactIds(); if (!empty($this->_params)) { @@ -2026,9 +2088,21 @@ public function whereClause($apiEntity = NULL) { // check for both id and contact_id if ($this->_params[$id][0] == 'id' || $this->_params[$id][0] == 'contact_id') { $this->_where[0][] = self::buildClause("contact_a.id", $this->_params[$id][1], $this->_params[$id][2]); + $field = CRM_Utils_Array::value('id', $this->_fields); + list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue( + 'CRM_Contact_BAO_Contact', + "contact_a.id", + $this->_params[$id][2], + $this->_params[$id][1] + ); + $this->_qill[0][] = ts("%1 %2 %3", [ + 1 => $field['title'], + 2 => $qillop, + 3 => $qillVal, + ]); } else { - $this->whereClauseSingle($this->_params[$id], $apiEntity); + $this->whereClauseSingle($this->_params[$id], $isForcePrimaryEmailOnly); } } @@ -2047,8 +2121,8 @@ public function whereClause($apiEntity = NULL) { $this->_qill = CRM_Utils_Array::crmArrayMerge($this->_qill, $this->_customQuery->_qill); } - $clauses = array(); - $andClauses = array(); + $clauses = []; + $andClauses = []; $validClauses = 0; if (!empty($this->_where)) { @@ -2089,10 +2163,10 @@ public function restWhere(&$values) { $wildcard = CRM_Utils_Array::value(4, $values); if (isset($grouping) && empty($this->_where[$grouping])) { - $this->_where[$grouping] = array(); + $this->_where[$grouping] = []; } - $multipleFields = array('url'); + $multipleFields = ['url']; //check if the location type exists for fields $lType = ''; @@ -2111,14 +2185,19 @@ public function restWhere(&$values) { $field = CRM_Utils_Array::value($locType[0], $this->_fields); if (!$field) { + // Strip any trailing _high & _low that might be appended. + $realFieldName = str_replace(['_high', '_low'], '', $name); + if (isset($this->_fields[$realFieldName])) { + $field = $this->_fields[str_replace(['_high', '_low'], '', $realFieldName)]; + $this->dateQueryBuilder($values, $field['table_name'], $realFieldName, $realFieldName, $field['title']); + } return; } } $setTables = TRUE; - $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'; - $locationType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); + $locationType = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); if (isset($locType[1]) && is_numeric($locType[1])) { $lType = $locationType[$locType[1]]; } @@ -2137,8 +2216,9 @@ public function restWhere(&$values) { } $this->_where[$grouping][] = self::buildClause($where, $op, $value); + $this->_tables[$aName] = $this->_whereTables[$aName] = 1; list($qillop, $qillVal) = self::buildQillForFieldValue('CRM_Core_DAO_Address', "state_province_id", $value, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['title'], 2 => $qillop, 3 => $qillVal)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['title'], 2 => $qillop, 3 => $qillVal]); } elseif (!empty($field['pseudoconstant'])) { $this->optionValueQuery( @@ -2165,9 +2245,10 @@ public function restWhere(&$values) { } $this->_where[$grouping][] = self::buildClause($where, $op, $value, 'Positive'); + $this->_tables[$aName] = $this->_whereTables[$aName] = 1; list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $name, $value, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['title'], 2 => $qillop, 3 => $qillVal)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['title'], 2 => $qillop, 3 => $qillVal]); } elseif ($name === 'world_region') { $this->optionValueQuery( @@ -2198,14 +2279,15 @@ public function restWhere(&$values) { } } elseif ($name === 'name') { - $value = $strtolower(CRM_Core_DAO::escapeString($value)); + $value = CRM_Core_DAO::escapeString($value); if ($wildcard) { $op = 'LIKE'; $value = self::getWildCardedValue($wildcard, $op, $value); } - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = self::caseImportant($op) ? "LOWER({$field['where']})" : "{$field['where']}"; - $this->_where[$grouping][] = self::buildClause($wc, $op, "'$value'"); + CRM_Core_Error::deprecatedFunctionWarning('Untested code path'); + // @todo it's likely this code path is obsolete / never called. It is definitely not + // passed through in our test suite. + $this->_where[$grouping][] = self::buildClause($field['where'], $op, "'$value'"); $this->_qill[$grouping][] = "$field[title] $op \"$value\""; } elseif ($name === 'current_employer') { @@ -2221,7 +2303,8 @@ public function restWhere(&$values) { $this->_qill[$grouping][] = "$field[title] $op \"$value\""; } elseif ($name === 'email_greeting') { - $filterCondition = array('greeting_type' => 'email_greeting'); + CRM_Core_Error::deprecatedFunctionWarning('pass in email_greeting_id or email_greeting_display'); + $filterCondition = ['greeting_type' => 'email_greeting']; $this->optionValueQuery( $name, $op, $value, $grouping, CRM_Core_PseudoConstant::greeting($filterCondition), @@ -2230,7 +2313,8 @@ public function restWhere(&$values) { ); } elseif ($name === 'postal_greeting') { - $filterCondition = array('greeting_type' => 'postal_greeting'); + CRM_Core_Error::deprecatedFunctionWarning('pass in postal_greeting_id or postal_greeting_display'); + $filterCondition = ['greeting_type' => 'postal_greeting']; $this->optionValueQuery( $name, $op, $value, $grouping, CRM_Core_PseudoConstant::greeting($filterCondition), @@ -2239,7 +2323,8 @@ public function restWhere(&$values) { ); } elseif ($name === 'addressee') { - $filterCondition = array('greeting_type' => 'addressee'); + CRM_Core_Error::deprecatedFunctionWarning('pass in addressee_id or addressee_display'); + $filterCondition = ['greeting_type' => 'addressee']; $this->optionValueQuery( $name, $op, $value, $grouping, CRM_Core_PseudoConstant::greeting($filterCondition), @@ -2250,7 +2335,7 @@ public function restWhere(&$values) { elseif (substr($name, 0, 4) === 'url-') { $tName = 'civicrm_website'; $this->_whereTables[$tName] = $this->_tables[$tName] = "\nLEFT JOIN civicrm_website ON ( civicrm_website.contact_id = contact_a.id )"; - $value = $strtolower(CRM_Core_DAO::escapeString($value)); + $value = CRM_Core_DAO::escapeString($value); if ($wildcard) { $op = 'LIKE'; $value = self::getWildCardedValue($wildcard, $op, $value); @@ -2277,8 +2362,7 @@ public function restWhere(&$values) { //get the location name list($tName, $fldName) = self::getLocationTableName($field['where'], $locType); - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $fieldName = "LOWER(`$tName`.$fldName)"; + $fieldName = "`$tName`.$fldName"; // we set both _tables & whereTables because whereTables doesn't seem to do what the name implies it should $this->_tables[$tName] = $this->_whereTables[$tName] = 1; @@ -2289,21 +2373,16 @@ public function restWhere(&$values) { $fieldName = "contact_a.{$fieldName}"; } else { - if ($op != 'IN' && !is_numeric($value) && !is_array($value)) { - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $fieldName = "LOWER({$field['where']})"; - } - else { - $fieldName = "{$field['where']}"; - } + $fieldName = $field['where']; } } list($qillop, $qillVal) = self::buildQillForFieldValue(NULL, $field['title'], $value, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array( + $this->_qill[$grouping][] = ts("%1 %2 %3", [ 1 => $field['title'], 2 => $qillop, - 3 => (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) ? $qillVal : "'$qillVal'")); + 3 => (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) ? $qillVal : "'$qillVal'", + ]); if (is_array($value)) { // traditionally an array being passed has been a fatal error. We can take advantage of this to add support @@ -2316,20 +2395,17 @@ public function restWhere(&$values) { //Via Contact get api value is not in array(operator => array(values)) format ONLY for IN/NOT IN operators //so this condition will satisfy the search for now if (strpos($op, 'IN') !== FALSE) { - $value = array($op => $value); + $value = [$op => $value]; } // we don't know when this might happen else { - CRM_Core_Error::fatal(ts("%1 is not a valid operator", array(1 => $operator))); + CRM_Core_Error::fatal(ts("%1 is not a valid operator", [1 => $operator])); } } } $this->_where[$grouping][] = CRM_Core_DAO::createSQLFilter($fieldName, $value, $type); } else { - if (!strpos($op, 'IN')) { - $value = $strtolower($value); - } if ($wildcard) { $op = 'LIKE'; $value = self::getWildCardedValue($wildcard, $op, $value); @@ -2360,8 +2436,8 @@ public static function getLocationTableName(&$where, &$locType) { list($tbName, $fldName) = explode(".", $where); //get the location name - $locationType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $specialFields = array('email', 'im', 'phone', 'openid', 'phone_ext'); + $locationType = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); + $specialFields = ['email', 'im', 'phone', 'openid', 'phone_ext']; if (in_array($locType[0], $specialFields)) { //hack to fix / special handing for phone_ext if ($locType[0] == 'phone_ext') { @@ -2375,7 +2451,7 @@ public static function getLocationTableName(&$where, &$locType) { } } elseif (in_array($locType[0], - array( + [ 'address_name', 'street_address', 'street_name', @@ -2390,18 +2466,18 @@ public static function getLocationTableName(&$where, &$locType) { 'geo_code_1', 'geo_code_2', 'master_id', - ) + ] )) { //fix for search by profile with address fields. $tName = "{$locationType[$locType[1]]}-address"; } elseif (in_array($locType[0], - array( + [ 'on_hold', 'signature_html', 'signature_text', 'is_bulkmail', - ) + ] )) { $tName = "{$locationType[$locType[1]]}-email"; } @@ -2415,7 +2491,7 @@ public static function getLocationTableName(&$where, &$locType) { $tName = "{$locationType[$locType[1]]}-{$locType[0]}"; } $tName = str_replace(' ', '_', $tName); - return array($tName, $fldName); + return [$tName, $fldName]; } CRM_Core_Error::fatal(); } @@ -2429,7 +2505,7 @@ public static function getLocationTableName(&$where, &$locType) { * values for this query */ public function store($dao) { - $value = array(); + $value = []; foreach ($this->_element as $key => $dontCare) { if (property_exists($dao, $key)) { @@ -2441,7 +2517,7 @@ public function store($dao) { $count = 1; foreach ($values as $v) { if (!array_key_exists($v, $current)) { - $current[$v] = array(); + $current[$v] = []; } //bad hack for im_provider if ($lastElement == 'provider_id') { @@ -2549,24 +2625,22 @@ public static function fromClause(&$tables, $inner = NULL, $right = NULL, $prima } if (!empty($tables['civicrm_worldregion'])) { - $tables = array_merge(array('civicrm_country' => 1), $tables); + $tables = array_merge(['civicrm_country' => 1], $tables); } if ((!empty($tables['civicrm_state_province']) || !empty($tables['civicrm_country']) || - CRM_Utils_Array::value('civicrm_county', $tables) - ) && empty($tables['civicrm_address']) - ) { - $tables = array_merge(array('civicrm_address' => 1), + CRM_Utils_Array::value('civicrm_county', $tables)) && empty($tables['civicrm_address'])) { + $tables = array_merge(['civicrm_address' => 1], $tables ); } // add group_contact and group table is subscription history is present if (!empty($tables['civicrm_subscription_history']) && empty($tables['civicrm_group'])) { - $tables = array_merge(array( - 'civicrm_group' => 1, - 'civicrm_group_contact' => 1, - ), + $tables = array_merge([ + 'civicrm_group' => 1, + 'civicrm_group_contact' => 1, + ], $tables ); } @@ -2600,7 +2674,7 @@ public static function fromClause(&$tables, $inner = NULL, $right = NULL, $prima $tempTable[$k . ".$key"] = $key; } ksort($tempTable); - $newTables = array(); + $newTables = []; foreach ($tempTable as $key) { $newTables[$key] = $tables[$key]; } @@ -2632,161 +2706,181 @@ public static function fromClause(&$tables, $inner = NULL, $right = NULL, $prima } continue; } - $searchPrimary = ''; - if (Civi::settings()->get('searchPrimaryDetailsOnly') || $apiEntity) { - $searchPrimary = "AND {$name}.is_primary = 1"; - } - switch ($name) { - case 'civicrm_address': - //CRM-14263 further handling of address joins further down... - if (!$primaryLocation) { - $searchPrimary = ''; - } - $from .= " $side JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id {$searchPrimary} )"; - continue; - case 'civicrm_phone': - $from .= " $side JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id {$searchPrimary}) "; - continue; + $from .= ' ' . trim(self::getEntitySpecificJoins($name, $mode, $side, $primaryLocation)) . ' '; + } + return $from; + } - case 'civicrm_email': - $from .= " $side JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id {$searchPrimary})"; - continue; + /** + * Get join statements for the from clause depending on entity type + * + * @param string $name + * @param int $mode + * @param string $side + * @param string $primaryLocation + * @return string + */ + protected static function getEntitySpecificJoins($name, $mode, $side, $primaryLocation) { + $limitToPrimaryClause = $primaryLocation ? "AND {$name}.is_primary = 1" : ''; + switch ($name) { + case 'civicrm_address': + //CRM-14263 further handling of address joins further down... + return " $side JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id {$limitToPrimaryClause} )"; - case 'civicrm_im': - $from .= " $side JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id {$searchPrimary}) "; - continue; + case 'civicrm_state_province': + // This is encountered when doing an export after having applied a 'sort' - it pretty much implies primary + // but that will have been implied-in by the calling function. + // test cover in testContactIDQuery + return " $side JOIN civicrm_state_province ON ( civicrm_address.state_province_id = civicrm_state_province.id )"; - case 'im_provider': - $from .= " $side JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id) "; - $from .= " $side JOIN civicrm_option_group option_group_imProvider ON option_group_imProvider.name = 'instant_messenger_service'"; - $from .= " $side JOIN civicrm_option_value im_provider ON (civicrm_im.provider_id = im_provider.value AND option_group_imProvider.id = im_provider.option_group_id)"; - continue; + case 'civicrm_country': + // This is encountered when doing an export after having applied a 'sort' - it pretty much implies primary + // but that will have been implied-in by the calling function. + // test cover in testContactIDQuery + return " $side JOIN civicrm_country ON ( civicrm_address.country_id = civicrm_country.id )"; - case 'civicrm_openid': - $from .= " $side JOIN civicrm_openid ON ( civicrm_openid.contact_id = contact_a.id {$searchPrimary} )"; - continue; + case 'civicrm_phone': + return " $side JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id {$limitToPrimaryClause}) "; - case 'civicrm_worldregion': - $from .= " $side JOIN civicrm_country ON civicrm_address.country_id = civicrm_country.id "; - $from .= " $side JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id "; - continue; + case 'civicrm_email': + return " $side JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id {$limitToPrimaryClause})"; - case 'civicrm_location_type': - $from .= " $side JOIN civicrm_location_type ON civicrm_address.location_type_id = civicrm_location_type.id "; - continue; + case 'civicrm_im': + return " $side JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id {$limitToPrimaryClause}) "; - case 'civicrm_group': - $from .= " $side JOIN civicrm_group ON civicrm_group.id = civicrm_group_contact.group_id "; - continue; + case 'im_provider': + $from = " $side JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id) "; + $from .= " $side JOIN civicrm_option_group option_group_imProvider ON option_group_imProvider.name = 'instant_messenger_service'"; + $from .= " $side JOIN civicrm_option_value im_provider ON (civicrm_im.provider_id = im_provider.value AND option_group_imProvider.id = im_provider.option_group_id)"; + return $from; - case 'civicrm_group_contact': - $from .= " $side JOIN civicrm_group_contact ON contact_a.id = civicrm_group_contact.contact_id "; - continue; + case 'civicrm_openid': + return " $side JOIN civicrm_openid ON ( civicrm_openid.contact_id = contact_a.id {$limitToPrimaryClause} )"; - case 'civicrm_group_contact_cache': - $from .= " $side JOIN civicrm_group_contact_cache ON contact_a.id = civicrm_group_contact_cache.contact_id "; - continue; + case 'civicrm_worldregion': + // We can be sure from the calling function that country will already be joined in. + // we really don't need world_region - we could use a pseudoconstant for it. + return " $side JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id "; - case 'civicrm_activity': - case 'civicrm_activity_tag': - case 'activity_type': - case 'activity_status': - case 'parent_id': - case 'civicrm_activity_contact': - case 'source_contact': - case 'activity_priority': - $from .= CRM_Activity_BAO_Query::from($name, $mode, $side); - continue; + case 'civicrm_location_type': + return " $side JOIN civicrm_location_type ON civicrm_address.location_type_id = civicrm_location_type.id "; - case 'civicrm_entity_tag': - $from .= " $side JOIN civicrm_entity_tag ON ( civicrm_entity_tag.entity_table = 'civicrm_contact' AND - civicrm_entity_tag.entity_id = contact_a.id ) "; - continue; + case 'civicrm_group': + return " $side JOIN civicrm_group ON civicrm_group.id = civicrm_group_contact.group_id "; - case 'civicrm_note': - $from .= " $side JOIN civicrm_note ON ( civicrm_note.entity_table = 'civicrm_contact' AND - contact_a.id = civicrm_note.entity_id ) "; - continue; + case 'civicrm_group_contact': + return " $side JOIN civicrm_group_contact ON contact_a.id = civicrm_group_contact.contact_id "; - case 'civicrm_subscription_history': - $from .= " $side JOIN civicrm_subscription_history - ON civicrm_group_contact.contact_id = civicrm_subscription_history.contact_id - AND civicrm_group_contact.group_id = civicrm_subscription_history.group_id"; - continue; + case 'civicrm_group_contact_cache': + return " $side JOIN civicrm_group_contact_cache ON contact_a.id = civicrm_group_contact_cache.contact_id "; - case 'civicrm_relationship': - if (self::$_relType == 'reciprocal') { - if (self::$_relationshipTempTable) { - // we have a temptable to join on - $tbl = self::$_relationshipTempTable; - $from .= " INNER JOIN {$tbl} civicrm_relationship ON civicrm_relationship.contact_id = contact_a.id"; - } - else { - $from .= " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = contact_a.id OR civicrm_relationship.contact_id_a = contact_a.id)"; - $from .= " $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_a = contact_b.id OR civicrm_relationship.contact_id_b = contact_b.id)"; - } - } - elseif (self::$_relType == 'b') { - $from .= " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = contact_a.id )"; - $from .= " $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_a = contact_b.id )"; + case 'civicrm_activity': + case 'civicrm_activity_tag': + case 'activity_type': + case 'activity_status': + case 'parent_id': + case 'civicrm_activity_contact': + case 'source_contact': + case 'activity_priority': + return CRM_Activity_BAO_Query::from($name, $mode, $side); + + case 'civicrm_entity_tag': + $from = " $side JOIN civicrm_entity_tag ON ( civicrm_entity_tag.entity_table = 'civicrm_contact'"; + return "$from AND civicrm_entity_tag.entity_id = contact_a.id ) "; + + case 'civicrm_note': + $from = " $side JOIN civicrm_note ON ( civicrm_note.entity_table = 'civicrm_contact'"; + return "$from AND contact_a.id = civicrm_note.entity_id ) "; + + case 'civicrm_subscription_history': + $from = " $side JOIN civicrm_subscription_history"; + $from .= " ON civicrm_group_contact.contact_id = civicrm_subscription_history.contact_id"; + return "$from AND civicrm_group_contact.group_id = civicrm_subscription_history.group_id"; + + case 'civicrm_relationship': + if (self::$_relType == 'reciprocal') { + if (self::$_relationshipTempTable) { + // we have a temptable to join on + $tbl = self::$_relationshipTempTable; + return " INNER JOIN {$tbl} civicrm_relationship ON civicrm_relationship.contact_id = contact_a.id"; } else { - $from .= " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_a = contact_a.id )"; - $from .= " $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_b = contact_b.id )"; + $from = " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = contact_a.id OR civicrm_relationship.contact_id_a = contact_a.id)"; + $from .= " $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_a = contact_b.id OR civicrm_relationship.contact_id_b = contact_b.id)"; + return $from; } - continue; + } + elseif (self::$_relType == 'b') { + $from = " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_b = contact_a.id )"; + return "$from $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_a = contact_b.id )"; + } + else { + $from = " $side JOIN civicrm_relationship ON (civicrm_relationship.contact_id_a = contact_a.id )"; + return "$from $side JOIN civicrm_contact contact_b ON (civicrm_relationship.contact_id_b = contact_b.id )"; + } - case 'civicrm_log': - $from .= " INNER JOIN civicrm_log ON (civicrm_log.entity_id = contact_a.id AND civicrm_log.entity_table = 'civicrm_contact')"; - $from .= " INNER JOIN civicrm_contact contact_b_log ON (civicrm_log.modified_id = contact_b_log.id)"; - continue; + case 'civicrm_log': + $from = " INNER JOIN civicrm_log ON (civicrm_log.entity_id = contact_a.id AND civicrm_log.entity_table = 'civicrm_contact')"; + return "$from INNER JOIN civicrm_contact contact_b_log ON (civicrm_log.modified_id = contact_b_log.id)"; - case 'civicrm_tag': - $from .= " $side JOIN civicrm_tag ON civicrm_entity_tag.tag_id = civicrm_tag.id "; - continue; + case 'civicrm_tag': + return " $side JOIN civicrm_tag ON civicrm_entity_tag.tag_id = civicrm_tag.id "; - case 'civicrm_grant': - $from .= CRM_Grant_BAO_Query::from($name, $mode, $side); - continue; + case 'civicrm_grant': + return CRM_Grant_BAO_Query::from($name, $mode, $side); - case 'civicrm_website': - $from .= " $side JOIN civicrm_website ON contact_a.id = civicrm_website.contact_id "; - continue; + case 'civicrm_website': + return " $side JOIN civicrm_website ON contact_a.id = civicrm_website.contact_id "; - default: - $locationTypeName = ''; - if (strpos($name, '-address') != 0) { - $locationTypeName = 'address'; - } - elseif (strpos($name, '-phone') != 0) { - $locationTypeName = 'phone'; - } - elseif (strpos($name, '-email') != 0) { - $locationTypeName = 'email'; - } - elseif (strpos($name, '-im') != 0) { - $locationTypeName = 'im'; - } - elseif (strpos($name, '-openid') != 0) { - $locationTypeName = 'openid'; - } + case 'civicrm_campaign': + //Move to default case if not in either mode. + if ($mode & CRM_Contact_BAO_Query::MODE_CONTRIBUTE) { + return CRM_Contribute_BAO_Query::from($name, $mode, $side); + } + elseif ($mode & CRM_Contact_BAO_Query::MODE_MAILING) { + return CRM_Mailing_BAO_Query::from($name, $mode, $side); + } + elseif ($mode & CRM_Contact_BAO_Query::MODE_CAMPAIGN) { + return CRM_Campaign_BAO_Query::from($name, $mode, $side); + } - if ($locationTypeName) { - //we have a join on an location table - possibly in conjunction with search builder - CRM-14263 - $parts = explode('-', $name); - $locationID = array_search($parts[0], CRM_Core_BAO_Address::buildOptions('location_type_id', 'get', array('name' => $parts[0]))); - $from .= " $side JOIN civicrm_{$locationTypeName} `{$name}` ON ( contact_a.id = `{$name}`.contact_id ) and `{$name}`.location_type_id = $locationID "; - } - else { - $from .= CRM_Core_Component::from($name, $mode, $side); + default: + $locationTypeName = ''; + if (strpos($name, '-address') != 0) { + $locationTypeName = 'address'; + } + elseif (strpos($name, '-phone') != 0) { + $locationTypeName = 'phone'; + } + elseif (strpos($name, '-email') != 0) { + $locationTypeName = 'email'; + } + elseif (strpos($name, '-im') != 0) { + $locationTypeName = 'im'; + } + elseif (strpos($name, '-openid') != 0) { + $locationTypeName = 'openid'; + } + + if ($locationTypeName) { + //we have a join on an location table - possibly in conjunction with search builder - CRM-14263 + $parts = explode('-', $name); + $locationTypes = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); + foreach ($locationTypes as $locationTypeID => $locationType) { + if ($parts[0] == str_replace(' ', '_', $locationType)) { + $locationID = $locationTypeID; + } } - $from .= CRM_Contact_BAO_Query_Hook::singleton()->buildSearchfrom($name, $mode, $side); + $from = " $side JOIN civicrm_{$locationTypeName} `{$name}` ON ( contact_a.id = `{$name}`.contact_id ) and `{$name}`.location_type_id = $locationID "; + } + else { + $from = CRM_Core_Component::from($name, $mode, $side); + } + $from .= CRM_Contact_BAO_Query_Hook::singleton()->buildSearchfrom($name, $mode, $side); - continue; - } + return $from; } - return $from; } /** @@ -2810,8 +2904,8 @@ public function deletedContacts($values) { public function contactType(&$values) { list($name, $op, $value, $grouping, $wildcard) = $values; - $subTypes = array(); - $clause = array(); + $subTypes = []; + $clause = []; // account for search builder mapping multiple values if (!is_array($value)) { @@ -2889,7 +2983,7 @@ public function includeContactSubTypes($value, $grouping, $op = 'LIKE') { $value = $value[$op]; } - $clause = array(); + $clause = []; $alias = "contact_a.contact_sub_type"; $qillOperators = CRM_Core_SelectValues::getSearchBuilderOperators(); @@ -2912,13 +3006,15 @@ public function includeContactSubTypes($value, $grouping, $op = 'LIKE') { if (!empty($clause)) { $this->_where[$grouping][] = "( " . implode(' OR ', $clause) . " )"; } - $this->_qill[$grouping][] = ts('Contact Subtype %1 ', array(1 => $qillOperators[$op])) . implode(' ' . ts('or') . ' ', array_keys($clause)); + $this->_qill[$grouping][] = ts('Contact Subtype %1 ', [1 => $qillOperators[$op]]) . implode(' ' . ts('or') . ' ', array_keys($clause)); } /** * Where / qill clause for groups. * * @param $values + * + * @throws \CRM_Core_Exception */ public function group($values) { list($name, $op, $value, $grouping, $wildcard) = $values; @@ -2928,17 +3024,26 @@ public function group($values) { $op = key($value); $value = $value[$op]; } - - // Replace pseudo operators from search builder - $op = str_replace('EMPTY', 'NULL', $op); + // Translate EMPTY to NULL as EMPTY is cannot be used in it's intended meaning here + // so has to be 'squashed into' NULL. (ie. group membership cannot be ''). + // even one group might equate to multiple when looking at children so IN is simpler. + // @todo - also look at != casting but there are rows below to review. + $opReplacements = [ + 'EMPTY' => 'NULL', + 'NOT EMPTY' => 'NOT NULL', + '=' => 'IN', + ]; + if (isset($opReplacements[$op])) { + $op = $opReplacements[$op]; + } if (strpos($op, 'NULL')) { $value = NULL; } - if (count($value) > 1) { + if (is_array($value) && count($value) > 1) { if (strpos($op, 'IN') === FALSE && strpos($op, 'NULL') === FALSE) { - CRM_Core_Error::fatal(ts("%1 is not a valid operator", array(1 => $op))); + CRM_Core_Error::fatal(ts("%1 is not a valid operator", [1 => $op])); } $this->_useDistinct = TRUE; } @@ -2951,76 +3056,112 @@ public function group($values) { $value = array_keys($this->getGroupsFromTypeCriteria($value)); } - $regularGroupIDs = $smartGroupIDs = array(); + $regularGroupIDs = $smartGroupIDs = []; foreach ((array) $value as $id) { if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $id, 'saved_search_id')) { - $smartGroupIDs[] = $id; + $smartGroupIDs[] = (int) $id; } else { - $regularGroupIDs[] = $id; + $regularGroupIDs[] = (int) trim($id); } } + $hasNonSmartGroups = count($regularGroupIDs); $isNotOp = ($op == 'NOT IN' || $op == '!='); - $statii = array(); - $gcsValues = $this->getWhereValues('group_contact_status', $grouping); - if ($gcsValues && - is_array($gcsValues[2]) - ) { - foreach ($gcsValues[2] as $k => $v) { - if ($v) { - $statii[] = "'" . CRM_Utils_Type::escape($k, 'String') . "'"; + $statusJoinClause = $this->getGroupStatusClause($grouping); + // If we are searching for 'Removed' contacts then despite it being a smart group we only care about the group_contact table. + $isGroupStatusSearch = (!empty($this->getSelectedGroupStatuses($grouping)) && $this->getSelectedGroupStatuses($grouping) !== ["'Added'"]); + $groupClause = []; + if ($hasNonSmartGroups || empty($value) || $isGroupStatusSearch) { + // include child groups IDs if any + $childGroupIds = (array) CRM_Contact_BAO_Group::getChildGroupIds($regularGroupIDs); + foreach ($childGroupIds as $key => $id) { + if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $id, 'saved_search_id')) { + $smartGroupIDs[] = $id; + unset($childGroupIds[$key]); } } - } - else { - $statii[] = "'Added'"; - } - $groupClause = array(); - if (count($regularGroupIDs) || empty($value)) { - $groupIds = implode(',', (array) $regularGroupIDs); - $gcTable = "`civicrm_group_contact-{$groupIds}`"; - $joinClause = array("contact_a.id = {$gcTable}.contact_id"); - if ($statii) { - $joinClause[] = "{$gcTable}.status IN (" . implode(', ', $statii) . ")"; + if (count($childGroupIds)) { + $regularGroupIDs = array_merge($regularGroupIDs, $childGroupIds); } - $this->_tables[$gcTable] = $this->_whereTables[$gcTable] = " LEFT JOIN civicrm_group_contact {$gcTable} ON (" . implode(' AND ', $joinClause) . ")"; - if (strpos($op, 'IN') !== FALSE) { - $clause = "{$gcTable}.group_id $op ( $groupIds ) %s "; + + if (empty($regularGroupIDs)) { + if ($isGroupStatusSearch) { + $regularGroupIDs = $smartGroupIDs; + } + // If it is still empty we want a filter that blocks all results. + if (empty($regularGroupIDs)) { + $regularGroupIDs = [0]; + } } - elseif ($op == '!=') { - $clause = "{$gcTable}.contact_id NOT IN (SELECT contact_id FROM civicrm_group_contact cgc WHERE cgc.group_id = $groupIds %s)"; + + $gcTable = "`civicrm_group_contact-" . uniqid() . "`"; + $joinClause = ["contact_a.id = {$gcTable}.contact_id"]; + + // @todo consider just casting != to NOT IN & handling both together. + if ($op == '!=') { + $groupIds = ''; + if (!empty($regularGroupIDs)) { + $groupIds = CRM_Utils_Type::validate( + implode(',', (array) $regularGroupIDs), + 'CommaSeparatedIntegers' + ); + } + $clause = "{$gcTable}.contact_id NOT IN (SELECT contact_id FROM civicrm_group_contact cgc WHERE cgc.group_id = $groupIds )"; } else { - $clause = "{$gcTable}.group_id $op $groupIds %s "; + $clause = self::buildClause("{$gcTable}.group_id", $op, $regularGroupIDs); } + $groupClause[] = "( {$clause} )"; - // include child groups IDs if any - $childGroupIds = CRM_Contact_BAO_Group::getChildGroupIds($regularGroupIDs); - $childClause = ''; - if (count($childGroupIds)) { - $gcTable = ($op == '!=') ? 'cgc' : $gcTable; - $childClause = " OR {$gcTable}.group_id IN (" . implode(',', $childGroupIds) . ") "; + if ($statusJoinClause) { + $joinClause[] = "{$gcTable}.$statusJoinClause"; } - $groupClause[] = '(' . sprintf($clause, $childClause) . ')'; + $this->_tables[$gcTable] = $this->_whereTables[$gcTable] = " LEFT JOIN civicrm_group_contact {$gcTable} ON (" . implode(' AND ', $joinClause) . ")"; } //CRM-19589: contact(s) removed from a Smart Group, resides in civicrm_group_contact table - if (count($smartGroupIDs)) { - $groupClause[] = " ( " . $this->addGroupContactCache($smartGroupIDs, NULL, "contact_a", $op) . " ) "; + // If we are only searching for Removed or Pending contacts we don't need to resolve the smart group + // as that info is in the group_contact table. + if ((count($smartGroupIDs) || empty($value)) && !$isGroupStatusSearch) { + $this->_groupUniqueKey = uniqid(); + $this->_groupKeys[] = $this->_groupUniqueKey; + $gccTableAlias = "civicrm_group_contact_cache_{$this->_groupUniqueKey}"; + $groupContactCacheClause = $this->addGroupContactCache($smartGroupIDs, $gccTableAlias, "contact_a", $op); + if (!empty($groupContactCacheClause)) { + if ($isNotOp) { + $groupIds = implode(',', (array) $smartGroupIDs); + $gcTable = "civicrm_group_contact_{$this->_groupUniqueKey}"; + $joinClause = ["contact_a.id = {$gcTable}.contact_id"]; + $this->_tables[$gcTable] = $this->_whereTables[$gcTable] = " LEFT JOIN civicrm_group_contact {$gcTable} ON (" . implode(' AND ', $joinClause) . ")"; + if (strpos($op, 'IN') !== FALSE) { + $groupClause[] = "{$gcTable}.group_id $op ( $groupIds ) AND {$gccTableAlias}.group_id IS NULL"; + } + else { + $groupClause[] = "{$gcTable}.group_id $op $groupIds AND {$gccTableAlias}.group_id IS NULL"; + } + } + $groupClause[] = " ( {$groupContactCacheClause} ) "; + } } $and = ($op == 'IS NULL') ? ' AND ' : ' OR '; - $this->_where[$grouping][] = ' ( ' . implode($and, $groupClause) . ' ) '; + if (!empty($groupClause)) { + $this->_where[$grouping][] = ' ( ' . implode($and, $groupClause) . ' ) '; + } list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contact_DAO_Group', 'id', $value, $op); - $this->_qill[$grouping][] = ts("Group(s) %1 %2", array(1 => $qillop, 2 => $qillVal)); + $this->_qill[$grouping][] = ts("Group(s) %1 %2", [1 => $qillop, 2 => $qillVal]); if (strpos($op, 'NULL') === FALSE) { - $this->_qill[$grouping][] = ts("Group Status %1", array(1 => implode(' ' . ts('or') . ' ', $statii))); + $this->_qill[$grouping][] = ts("Group Status %1", [1 => implode(' ' . ts('or') . ' ', $this->getSelectedGroupStatuses($grouping))]); } } + public function getGroupCacheTableKeys() { + return $this->_groupKeys; + } + /** * Function translates selection of group type into a list of groups. * @param $value @@ -3028,7 +3169,7 @@ public function group($values) { * @return array */ public function getGroupsFromTypeCriteria($value) { - $groupIds = array(); + $groupIds = []; foreach ((array) $value as $groupTypeValue) { $groupList = CRM_Core_PseudoConstant::group($groupTypeValue); $groupIds = ($groupIds + $groupList); @@ -3037,24 +3178,33 @@ public function getGroupsFromTypeCriteria($value) { } /** - * @param array $groups - * @param string $tableAlias - * @param string $joinTable - * @param string $op + * Prime smart group cache for smart groups in the search, and join + * civicrm_group_contact_cache table into the query. + * + * @param array $groups IDs of groups specified in search criteria. + * @param string $tableAlias Alias to use for civicrm_group_contact_cache table. + * @param string $joinTable Table on which to join civicrm_group_contact_cache + * @param string $op SQL comparison operator (NULL, IN, !=, IS NULL, etc.) + * @param string $joinColumn Column in $joinTable on which to join civicrm_group_contact_cache.contact_id * - * @return null|string + * @return string WHERE clause component for smart group criteria. */ - public function addGroupContactCache($groups, $tableAlias = NULL, $joinTable = "contact_a", $op) { + public function addGroupContactCache($groups, $tableAlias, $joinTable = "contact_a", $op, $joinColumn = 'id') { $isNullOp = (strpos($op, 'NULL') !== FALSE); $groupsIds = $groups; + + $operator = ['=' => 'IN', '!=' => 'NOT IN']; + if (!empty($operator[$op]) && is_array($groups)) { + $op = $operator[$op]; + } if (!$isNullOp && !$groups) { return NULL; } elseif (strpos($op, 'IN') !== FALSE) { - $groups = array($op => $groups); + $groups = [$op => $groups]; } elseif (is_array($groups) && count($groups)) { - $groups = array('IN' => $groups); + $groups = ['IN' => $groups]; } // Find all the groups that are part of a saved search. @@ -3076,16 +3226,15 @@ public function addGroupContactCache($groups, $tableAlias = NULL, $joinTable = " CRM_Contact_BAO_GroupContactCache::load($group); } } - if ($group->N == 0) { + if ($group->N == 0 && $op != 'NOT IN') { return NULL; } - if (!$tableAlias) { - $tableAlias = "`civicrm_group_contact_cache_"; - $tableAlias .= ($isNullOp) ? "a`" : implode(',', (array) $groupsIds) . "`"; - } + $this->_tables[$tableAlias] = $this->_whereTables[$tableAlias] = " LEFT JOIN civicrm_group_contact_cache {$tableAlias} ON {$joinTable}.{$joinColumn} = {$tableAlias}.contact_id "; - $this->_tables[$tableAlias] = $this->_whereTables[$tableAlias] = " LEFT JOIN civicrm_group_contact_cache {$tableAlias} ON {$joinTable}.id = {$tableAlias}.contact_id "; + if ($op == 'NOT IN') { + return "{$tableAlias}.contact_id NOT IN (SELECT contact_id FROM civicrm_group_contact_cache cgcc WHERE cgcc.group_id IN ( " . implode(',', (array) $groupsIds) . " ) )"; + } return self::buildClause("{$tableAlias}.group_id", $op, $groups, 'Int'); } @@ -3120,12 +3269,13 @@ public function tagSearch(&$values) { $op = "LIKE"; $value = "%{$value}%"; + $escapedValue = CRM_Utils_Type::escape("%{$value}%", 'String'); $useAllTagTypes = $this->getWhereValues('all_tag_types', $grouping); $tagTypesText = $this->getWhereValues('tag_types_text', $grouping); - $etTable = "`civicrm_entity_tag-" . $value . "`"; - $tTable = "`civicrm_tag-" . $value . "`"; + $etTable = "`civicrm_entity_tag-" . uniqid() . "`"; + $tTable = "`civicrm_tag-" . uniqid() . "`"; if ($useAllTagTypes[2]) { $this->_tables[$etTable] = $this->_whereTables[$etTable] @@ -3133,8 +3283,8 @@ public function tagSearch(&$values) { LEFT JOIN civicrm_tag {$tTable} ON ( {$etTable}.tag_id = {$tTable}.id )"; // search tag in cases - $etCaseTable = "`civicrm_entity_case_tag-" . $value . "`"; - $tCaseTable = "`civicrm_case_tag-" . $value . "`"; + $etCaseTable = "`civicrm_entity_case_tag-" . uniqid() . "`"; + $tCaseTable = "`civicrm_case_tag-" . uniqid() . "`"; $this->_tables[$etCaseTable] = $this->_whereTables[$etCaseTable] = " LEFT JOIN civicrm_case_contact ON civicrm_case_contact.contact_id = contact_a.id LEFT JOIN civicrm_case @@ -3143,9 +3293,9 @@ public function tagSearch(&$values) { LEFT JOIN civicrm_entity_tag {$etCaseTable} ON ( {$etCaseTable}.entity_table = 'civicrm_case' AND {$etCaseTable}.entity_id = civicrm_case.id ) LEFT JOIN civicrm_tag {$tCaseTable} ON ( {$etCaseTable}.tag_id = {$tCaseTable}.id )"; // search tag in activities - $etActTable = "`civicrm_entity_act_tag-" . $value . "`"; - $tActTable = "`civicrm_act_tag-" . $value . "`"; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $etActTable = "`civicrm_entity_act_tag-" . uniqid() . "`"; + $tActTable = "`civicrm_act_tag-" . uniqid() . "`"; + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $this->_tables[$etActTable] = $this->_whereTables[$etActTable] @@ -3157,18 +3307,18 @@ public function tagSearch(&$values) { LEFT JOIN civicrm_entity_tag as {$etActTable} ON ( {$etActTable}.entity_table = 'civicrm_activity' AND {$etActTable}.entity_id = civicrm_activity.id ) LEFT JOIN civicrm_tag {$tActTable} ON ( {$etActTable}.tag_id = {$tActTable}.id )"; - $this->_where[$grouping][] = "({$tTable}.name $op '" . $value . "' OR {$tCaseTable}.name $op '" . $value . "' OR {$tActTable}.name $op '" . $value . "')"; - $this->_qill[$grouping][] = ts('Tag %1 %2', array(1 => $tagTypesText[2], 2 => $op)) . ' ' . $value; + $this->_where[$grouping][] = "({$tTable}.name $op '" . $escapedValue . "' OR {$tCaseTable}.name $op '" . $escapedValue . "' OR {$tActTable}.name $op '" . $escapedValue . "')"; + $this->_qill[$grouping][] = ts('Tag %1 %2', [1 => $tagTypesText[2], 2 => $op]) . ' ' . $value; } else { - $etTable = "`civicrm_entity_tag-" . $value . "`"; - $tTable = "`civicrm_tag-" . $value . "`"; + $etTable = "`civicrm_entity_tag-" . uniqid() . "`"; + $tTable = "`civicrm_tag-" . uniqid() . "`"; $this->_tables[$etTable] = $this->_whereTables[$etTable] = " LEFT JOIN civicrm_entity_tag {$etTable} ON ( {$etTable}.entity_id = contact_a.id AND {$etTable}.entity_table = 'civicrm_contact' ) LEFT JOIN civicrm_tag {$tTable} ON ( {$etTable}.tag_id = {$tTable}.id ) "; $this->_where[$grouping][] = self::buildClause("{$tTable}.name", $op, $value, 'String'); - $this->_qill[$grouping][] = ts('Tagged %1', array(1 => $op)) . ' ' . $value; + $this->_qill[$grouping][] = ts('Tagged %1', [1 => $op]) . ' ' . $value; } } @@ -3180,7 +3330,7 @@ public function tagSearch(&$values) { public function tag(&$values) { list($name, $op, $value, $grouping, $wildcard) = $values; - list($qillop, $qillVal) = self::buildQillForFieldValue('CRM_Core_DAO_EntityTag', "tag_id", $value, $op, array('onlyActive' => FALSE)); + list($qillop, $qillVal) = self::buildQillForFieldValue('CRM_Core_DAO_EntityTag', "tag_id", $value, $op, ['onlyActive' => FALSE]); // API/Search Builder format array(operator => array(values)) if (is_array($value)) { if (in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) { @@ -3190,21 +3340,29 @@ public function tag(&$values) { if (count($value) > 1) { $this->_useDistinct = TRUE; } - $value = implode(',', (array) $value); + } + + // implode array, then remove all spaces + $value = str_replace(' ', '', implode(',', (array) $value)); + if (!empty($value)) { + $value = CRM_Utils_Type::validate( + $value, + 'CommaSeparatedIntegers' + ); } $useAllTagTypes = $this->getWhereValues('all_tag_types', $grouping); $tagTypesText = $this->getWhereValues('tag_types_text', $grouping); - $etTable = "`civicrm_entity_tag-" . $value . "`"; + $etTable = "`civicrm_entity_tag-" . uniqid() . "`"; if ($useAllTagTypes[2]) { $this->_tables[$etTable] = $this->_whereTables[$etTable] = " LEFT JOIN civicrm_entity_tag {$etTable} ON ( {$etTable}.entity_id = contact_a.id AND {$etTable}.entity_table = 'civicrm_contact') "; // search tag in cases - $etCaseTable = "`civicrm_entity_case_tag-" . $value . "`"; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $etCaseTable = "`civicrm_entity_case_tag-" . uniqid() . "`"; + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $this->_tables[$etCaseTable] = $this->_whereTables[$etCaseTable] @@ -3214,7 +3372,7 @@ public function tag(&$values) { AND civicrm_case.is_deleted = 0 ) LEFT JOIN civicrm_entity_tag {$etCaseTable} ON ( {$etCaseTable}.entity_table = 'civicrm_case' AND {$etCaseTable}.entity_id = civicrm_case.id ) "; // search tag in activities - $etActTable = "`civicrm_entity_act_tag-" . $value . "`"; + $etActTable = "`civicrm_entity_act_tag-" . uniqid() . "`"; $this->_tables[$etActTable] = $this->_whereTables[$etActTable] = " LEFT JOIN civicrm_activity_contact ON ( civicrm_activity_contact.contact_id = contact_a.id AND civicrm_activity_contact.record_type_id = {$targetID} ) @@ -3224,7 +3382,7 @@ public function tag(&$values) { LEFT JOIN civicrm_entity_tag as {$etActTable} ON ( {$etActTable}.entity_table = 'civicrm_activity' AND {$etActTable}.entity_id = civicrm_activity.id ) "; // CRM-10338 - if (in_array($op, array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) { + if (in_array($op, ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'])) { $this->_where[$grouping][] = "({$etTable}.tag_id $op OR {$etCaseTable}.tag_id $op OR {$etActTable}.tag_id $op)"; } else { @@ -3236,7 +3394,7 @@ public function tag(&$values) { = " LEFT JOIN civicrm_entity_tag {$etTable} ON ( {$etTable}.entity_id = contact_a.id AND {$etTable}.entity_table = 'civicrm_contact') "; // CRM-10338 - if (in_array($op, array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) { + if (in_array($op, ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'])) { // this converts IS (NOT)? EMPTY to IS (NOT)? NULL $op = str_replace('EMPTY', 'NULL', $op); $this->_where[$grouping][] = "{$etTable}.tag_id $op"; @@ -3250,7 +3408,7 @@ public function tag(&$values) { $this->_where[$grouping][] = "{$etTable}.tag_id $op ( $value )"; } } - $this->_qill[$grouping][] = ts('Tagged %1 %2', array(1 => $qillop, 2 => $qillVal)); + $this->_qill[$grouping][] = ts('Tagged %1 %2', [1 => $qillop, 2 => $qillVal]); } /** @@ -3270,9 +3428,8 @@ public function notes(&$values) { $this->_tables['civicrm_note'] = $this->_whereTables['civicrm_note'] = " LEFT JOIN civicrm_note ON ( civicrm_note.entity_table = 'civicrm_contact' AND contact_a.id = civicrm_note.entity_id ) "; - $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'; $n = trim($value); - $value = $strtolower(CRM_Core_DAO::escapeString($n)); + $value = CRM_Core_DAO::escapeString($n); if ($wildcard) { if (strpos($value, '%') === FALSE) { $value = "%$value%"; @@ -3284,7 +3441,7 @@ public function notes(&$values) { } $label = NULL; - $clauses = array(); + $clauses = []; if ($noteOption % 2 == 0) { $clauses[] = self::buildClause('civicrm_note.note', $op, $value, 'String'); $label = ts('Note: Body Only'); @@ -3295,7 +3452,7 @@ public function notes(&$values) { } $this->_where[$grouping][] = "( " . implode(' OR ', $clauses) . " )"; list($qillOp, $qillVal) = self::buildQillForFieldValue(NULL, $name, $n, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $label, 2 => $qillOp, 3 => $qillVal)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $label, 2 => $qillOp, 3 => $qillVal]); } /** @@ -3341,7 +3498,7 @@ public function sortName(&$values) { return; } - $input = $value = trim($value); + $input = $value = is_array($value) ? trim($value['LIKE']) : trim($value); if (!strlen($value)) { return; @@ -3349,16 +3506,14 @@ public function sortName(&$values) { $config = CRM_Core_Config::singleton(); - $sub = array(); + $sub = []; //By default, $sub elements should be joined together with OR statements (don't change this variable). $subGlue = ' OR '; - $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'; - $firstChar = substr($value, 0, 1); $lastChar = substr($value, -1, 1); - $quotes = array("'", '"'); + $quotes = ["'", '"']; // If string is quoted, strip quotes and otherwise don't alter it if ((strlen($value) > 2) && in_array($firstChar, $quotes) && in_array($lastChar, $quotes)) { $value = trim($value, implode('', $quotes)); @@ -3368,22 +3523,19 @@ public function sortName(&$values) { elseif ($op == 'LIKE' && strpos($value, ',') === FALSE) { $value = str_replace(' ', '%', $value); } - $value = $strtolower(CRM_Core_DAO::escapeString(trim($value))); + $value = CRM_Core_DAO::escapeString(trim($value)); if (strlen($value)) { - $fieldsub = array(); + $fieldsub = []; $value = "'" . self::getWildCardedValue($wildcard, $op, $value) . "'"; if ($fieldName == 'sort_name') { - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = self::caseImportant($op) ? "LOWER(contact_a.sort_name)" : "contact_a.sort_name"; + $wc = "contact_a.sort_name"; } else { - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = self::caseImportant($op) ? "LOWER(contact_a.display_name)" : "contact_a.display_name"; + $wc = "contact_a.display_name"; } $fieldsub[] = " ( $wc $op $value )"; if ($config->includeNickNameInName) { - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = self::caseImportant($op) ? "LOWER(contact_a.nick_name)" : "contact_a.nick_name"; + $wc = "contact_a.nick_name"; $fieldsub[] = " ( $wc $op $value )"; } if ($config->includeEmailInName) { @@ -3414,7 +3566,7 @@ public function greetings(&$values) { $name .= '_display'; list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $name, $value, $op); - $this->_qill[$grouping][] = ts('Greeting %1 %2', array(1 => $qillop, 2 => $qillVal)); + $this->_qill[$grouping][] = ts('Greeting %1 %2', [1 => $qillop, 2 => $qillVal]); $this->_where[$grouping][] = self::buildClause("contact_a.{$name}", $op, $value, 'String'); } @@ -3422,27 +3574,30 @@ public function greetings(&$values) { * Where / qill clause for email * * @param array $values - * @param string $apiEntity + * @param string $isForcePrimaryOnly + * + * @throws \CRM_Core_Exception */ - protected function email(&$values, $apiEntity) { + protected function email(&$values, $isForcePrimaryOnly) { list($name, $op, $value, $grouping, $wildcard) = $values; $this->_tables['civicrm_email'] = $this->_whereTables['civicrm_email'] = 1; // CRM-18147: for Contact's GET API, email fieldname got appended with its entity as in {$apiEntiy}_{$name} // so following code is use build whereClause for contact's primart email id - if (!empty($apiEntity)) { - $dataType = 'String'; - if ($name == 'email_id') { - $dataType = 'Integer'; - $name = 'id'; - } - + if (!empty($isForcePrimaryOnly)) { $this->_where[$grouping][] = self::buildClause('civicrm_email.is_primary', '=', 1, 'Integer'); - $this->_where[$grouping][] = self::buildClause("civicrm_email.$name", $op, $value, $dataType); + } + // @todo - this should come from the $this->_fields array + $dbName = $name === 'email_id' ? 'id' : $name; + + if (is_array($value) || $name === 'email_id') { + $this->_qill[$grouping][] = $this->getQillForField($name, $value, $op); + $this->_where[$grouping][] = self::buildClause('civicrm_email.' . $dbName, $op, $value, 'String'); return; } - $n = strtolower(trim($value)); + // Is this ever hit now? Ideally ensure always an array & handle above. + $n = trim($value); if ($n) { if (substr($n, 0, 1) == '"' && substr($n, -1, 1) == '"' @@ -3504,7 +3659,7 @@ public function phone_option_group($values) { * @param array $values */ public function street_address(&$values) { - list($name, $op, $value, $grouping, $wildcard) = $values; + list($name, $op, $value, $grouping) = $values; if (!$op) { $op = 'LIKE'; @@ -3513,14 +3668,12 @@ public function street_address(&$values) { $n = trim($value); if ($n) { - $value = strtolower($n); if (strpos($value, '%') === FALSE) { // only add wild card if not there $value = "%{$value}%"; } $op = 'LIKE'; - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $this->_where[$grouping][] = self::buildClause('LOWER(civicrm_address.street_address)', $op, $value, 'String'); + $this->_where[$grouping][] = self::buildClause('civicrm_address.street_address', $op, $value, 'String'); $this->_qill[$grouping][] = ts('Street') . " $op '$n'"; } else { @@ -3554,10 +3707,8 @@ public function street_number(&$values) { $this->_qill[$grouping][] = ts('Street Number is even'); } else { - $value = strtolower($n); - - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $this->_where[$grouping][] = self::buildClause('LOWER(civicrm_address.street_number)', $op, $value, 'String'); + $value = $n; + $this->_where[$grouping][] = self::buildClause('civicrm_address.street_number', $op, $value, 'String'); $this->_qill[$grouping][] = ts('Street Number') . " $op '$n'"; } @@ -3573,9 +3724,9 @@ public function sortByCharacter(&$values) { list($name, $op, $value, $grouping, $wildcard) = $values; $name = trim($value); - $cond = " contact_a.sort_name LIKE '" . strtolower(CRM_Core_DAO::escapeWildCardString($name)) . "%'"; + $cond = " contact_a.sort_name LIKE '" . CRM_Core_DAO::escapeWildCardString($name) . "%'"; $this->_where[$grouping][] = $cond; - $this->_qill[$grouping][] = ts('Showing only Contacts starting with: \'%1\'', array(1 => $name)); + $this->_qill[$grouping][] = ts('Showing only Contacts starting with: \'%1\'', [1 => $name]); } /** @@ -3586,7 +3737,7 @@ public function includeContactIDs() { return; } - $contactIds = array(); + $contactIds = []; foreach ($this->_params as $id => $values) { if (substr($values[0], 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) { $contactIds[] = substr($values[0], CRM_Core_Form::CB_PREFIX_LEN); @@ -3613,7 +3764,7 @@ public function postalCode(&$values) { // Handle numeric postal code range searches properly by casting the column as numeric if (is_numeric($value)) { - $field = "IF (civicrm_address.postal_code REGEXP '^[0-9]+$', CAST(civicrm_address.postal_code AS UNSIGNED), 0)"; + $field = "IF (civicrm_address.postal_code REGEXP '^[0-9]{1,10}$', CAST(civicrm_address.postal_code AS UNSIGNED), 0)"; $val = CRM_Utils_Type::escape($value, 'Integer'); } else { @@ -3636,11 +3787,11 @@ public function postalCode(&$values) { } elseif ($name == 'postal_code_low') { $this->_where[$grouping][] = " ( $field >= '$val' ) "; - $this->_qill[$grouping][] = ts('Postal code greater than or equal to \'%1\'', array(1 => $value)); + $this->_qill[$grouping][] = ts('Postal code greater than or equal to \'%1\'', [1 => $value]); } elseif ($name == 'postal_code_high') { $this->_where[$grouping][] = " ( $field <= '$val' ) "; - $this->_qill[$grouping][] = ts('Postal code less than or equal to \'%1\'', array(1 => $value)); + $this->_qill[$grouping][] = ts('Postal code less than or equal to \'%1\'', [1 => $value]); } } @@ -3661,7 +3812,7 @@ public function locationType(&$values, $status = NULL) { $this->_whereTables['civicrm_address'] = 1; $locationType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $names = array(); + $names = []; foreach ($value as $id) { $names[] = $locationType[$id]; } @@ -3696,13 +3847,13 @@ public function country(&$values, $fromStateProvince = TRUE) { } $countryClause = $countryQill = NULL; - if ($values && !empty($value)) { + if (in_array($op, ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY']) || ($values && !empty($value))) { $this->_tables['civicrm_address'] = 1; $this->_whereTables['civicrm_address'] = 1; $countryClause = self::buildClause('civicrm_address.country_id', $op, $value, 'Positive'); list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, 'country_id', $value, $op); - $countryQill = ts("%1 %2 %3", array(1 => 'Country', 2 => $qillop, 3 => $qillVal)); + $countryQill = ts("%1 %2 %3", [1 => 'Country', 2 => $qillop, 3 => $qillVal]); if (!$fromStateProvince) { $this->_where[$grouping][] = $countryClause; @@ -3712,13 +3863,13 @@ public function country(&$values, $fromStateProvince = TRUE) { if ($fromStateProvince) { if (!empty($countryClause)) { - return array( + return [ $countryClause, " ...AND... " . $countryQill, - ); + ]; } else { - return array(NULL, NULL); + return [NULL, NULL]; } } } @@ -3736,7 +3887,7 @@ public function county(&$values, $status = NULL) { if (!is_array($value)) { // force the county to be an array - $value = array($value); + $value = [$value]; } // check if the values are ids OR names of the counties @@ -3747,7 +3898,7 @@ public function county(&$values, $status = NULL) { break; } } - $names = array(); + $names = []; if ($op == '=') { $op = 'IN'; } @@ -3759,7 +3910,7 @@ public function county(&$values, $status = NULL) { $op = str_replace('EMPTY', 'NULL', $op); } - if (in_array($op, array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) { + if (in_array($op, ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'])) { $clause = "civicrm_address.county_id $op"; } elseif ($inputFormat == 'id') { @@ -3771,7 +3922,7 @@ public function county(&$values, $status = NULL) { } } else { - $inputClause = array(); + $inputClause = []; $county = CRM_Core_PseudoConstant::county(); foreach ($value as $name) { $name = trim($name); @@ -3819,7 +3970,7 @@ public function stateProvince(&$values, $status = NULL) { $this->_where[$grouping][] = $clause; list($qillop, $qillVal) = self::buildQillForFieldValue('CRM_Core_DAO_Address', "state_province_id", $value, $op); if (!$status) { - $this->_qill[$grouping][] = ts("State/Province %1 %2 %3", array(1 => $qillop, 2 => $qillVal, 3 => $countryQill)); + $this->_qill[$grouping][] = ts("State/Province %1 %2 %3", [1 => $qillop, 2 => $qillVal, 3 => $countryQill]); } else { return implode(' ' . ts('or') . ' ', $qillVal) . $countryQill; @@ -3840,11 +3991,25 @@ public function changeLog(&$values) { } $name = trim($targetName[2]); - $name = strtolower(CRM_Core_DAO::escapeString($name)); + $name = CRM_Core_DAO::escapeString($name); $name = $targetName[4] ? "%$name%" : $name; $this->_where[$grouping][] = "contact_b_log.sort_name LIKE '%$name%'"; $this->_tables['civicrm_log'] = $this->_whereTables['civicrm_log'] = 1; - $this->_qill[$grouping][] = ts('Modified By') . " $name"; + $fieldTitle = ts('Added By'); + foreach ($this->_params as $params) { + if ($params[0] == 'log_date') { + if ($params[2] == 2) { + $fieldTitle = ts('Modified By'); + } + break; + } + } + list($qillop, $qillVal) = self::buildQillForFieldValue(NULL, 'changed_by', $name, 'LIKE'); + $this->_qill[$grouping][] = ts("%1 %2 '%3'", [ + 1 => $fieldTitle, + 2 => $qillop, + 3 => $qillVal, + ]); } /** @@ -3901,7 +4066,7 @@ public function demographics(&$values) { * @param $values */ public function privacy(&$values) { - list($name, $op, $value, $grouping, $wildcard) = $values; + list($name, $op, $value, $grouping) = $values; //fixed for profile search listing CRM-4633 if (strpbrk($value, "[")) { $value = "'{$value}'"; @@ -3933,6 +4098,7 @@ public function privacyOptions($values) { if ($opValues && strtolower($opValues[2] == 'AND') ) { + // @todo this line is logially unreachable $operator = 'AND'; } @@ -3944,8 +4110,8 @@ public function privacyOptions($values) { $compareOP = ''; } - $clauses = array(); - $qill = array(); + $clauses = []; + $qill = []; foreach ($value as $dontCare => $pOption) { $clauses[] = " ( contact_a.{$pOption} = 1 ) "; $field = CRM_Utils_Array::value($pOption, $this->_fields); @@ -3964,7 +4130,7 @@ public function preferredCommunication(&$values) { list($name, $op, $value, $grouping, $wildcard) = $values; if (!is_array($value)) { - $value = str_replace(array('(', ')'), '', explode(",", $value)); + $value = str_replace(['(', ')'], '', explode(",", $value)); } elseif (in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) { $op = key($value); @@ -3979,7 +4145,7 @@ public function preferredCommunication(&$values) { } $this->_where[$grouping][] = self::buildClause("contact_a.preferred_communication_method", $op, $value); - $this->_qill[$grouping][] = ts('Preferred Communication Method %1 %2', array(1 => $qillop, 2 => $qillVal)); + $this->_qill[$grouping][] = ts('Preferred Communication Method %1 %2', [1 => $qillop, 2 => $qillVal]); } /** @@ -3995,9 +4161,9 @@ public function relationship(&$values) { // also get values array for relation_target_name // for relationship search we always do wildcard $relationType = $this->getWhereValues('relation_type_id', $grouping); + $description = $this->getWhereValues('relation_description', $grouping); $targetName = $this->getWhereValues('relation_target_name', $grouping); $relStatus = $this->getWhereValues('relation_status', $grouping); - $relPermission = $this->getWhereValues('relation_permission', $grouping); $targetGroup = $this->getWhereValues('relation_target_group', $grouping); $nameClause = $name = NULL; @@ -4007,48 +4173,65 @@ public function relationship(&$values) { substr($name, -1, 1) == '"' ) { $name = substr($name, 1, -1); - $name = strtolower(CRM_Core_DAO::escapeString($name)); + $name = CRM_Core_DAO::escapeString($name); $nameClause = "= '$name'"; } else { - $name = strtolower(CRM_Core_DAO::escapeString($name)); + $name = CRM_Core_DAO::escapeString($name); $nameClause = "LIKE '%{$name}%'"; } } - $rTypeValues = array(); + $relTypes = $relTypesIds = []; if (!empty($relationType)) { - $rel = explode('_', $relationType[2]); - self::$_relType = $rel[1]; - $params = array('id' => $rel[0]); - $rType = CRM_Contact_BAO_RelationshipType::retrieve($params, $rTypeValues); - } - if (!empty($rTypeValues) && $rTypeValues['name_a_b'] == $rTypeValues['name_b_a']) { - // if we don't know which end of the relationship we are dealing with we'll create a temp table - //@todo unless we are dealing with a target group - self::$_relType = 'reciprocal'; + $relationType[2] = (array) $relationType[2]; + foreach ($relationType[2] as $relType) { + $rel = explode('_', $relType); + self::$_relType = $rel[1]; + $params = ['id' => $rel[0]]; + $typeValues = []; + $rTypeValue = CRM_Contact_BAO_RelationshipType::retrieve($params, $typeValues); + if (!empty($rTypeValue)) { + if ($rTypeValue->name_a_b == $rTypeValue->name_b_a) { + // if we don't know which end of the relationship we are dealing with we'll create a temp table + self::$_relType = 'reciprocal'; + } + $relTypesIds[] = $rel[0]; + $relTypes[] = $relType; + } + } } + // if we are creating a temp table we build our own where for the relationship table $relationshipTempTable = NULL; - if (self::$_relType == 'reciprocal' && empty($targetGroup)) { - $where = array(); + if (self::$_relType == 'reciprocal') { + $where = []; self::$_relationshipTempTable = $relationshipTempTable = CRM_Core_DAO::createTempTableName('civicrm_rel'); if ($nameClause) { $where[$grouping][] = " sort_name $nameClause "; } + $groupJoinTable = "civicrm_relationship"; + $groupJoinColumn = "contact_id_alt"; } else { $where = &$this->_where; if ($nameClause) { $where[$grouping][] = "( contact_b.sort_name $nameClause AND contact_b.id != contact_a.id )"; } + $groupJoinTable = "contact_b"; + $groupJoinColumn = "id"; } - $allRelationshipType = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, 'null', NULL, NULL, TRUE); - if ($nameClause || !$targetGroup) { if (!empty($relationType)) { - $this->_qill[$grouping][] = $allRelationshipType[$relationType[2]] . " $name"; + $relQill = ''; + foreach ($relTypes as $rel) { + if (!empty($relQill)) { + $relQill .= ' OR '; + } + $relQill .= $allRelationshipType[$rel]; + } + $this->_qill[$grouping][] = 'Relationship Type(s) ' . $relQill . " $name"; } else { $this->_qill[$grouping][] = $name; @@ -4059,12 +4242,12 @@ public function relationship(&$values) { if ($targetGroup) { //add contacts from static groups $this->_tables['civicrm_relationship_group_contact'] = $this->_whereTables['civicrm_relationship_group_contact'] - = " LEFT JOIN civicrm_group_contact civicrm_relationship_group_contact ON civicrm_relationship_group_contact.contact_id = contact_b.id AND civicrm_relationship_group_contact.status = 'Added'"; + = " LEFT JOIN civicrm_group_contact civicrm_relationship_group_contact ON civicrm_relationship_group_contact.contact_id = {$groupJoinTable}.{$groupJoinColumn} AND civicrm_relationship_group_contact.status = 'Added'"; $groupWhere[] = "( civicrm_relationship_group_contact.group_id IN (" . implode(",", $targetGroup[2]) . ") ) "; //add contacts from saved searches - $ssWhere = $this->addGroupContactCache($targetGroup[2], "civicrm_relationship_group_contact_cache", "contact_b", $op); + $ssWhere = $this->addGroupContactCache($targetGroup[2], "civicrm_relationship_group_contact_cache", $groupJoinTable, $op, $groupJoinColumn); //set the group where clause if ($ssWhere) { @@ -4074,20 +4257,34 @@ public function relationship(&$values) { //Get the names of the target groups for the qill $groupNames = CRM_Core_PseudoConstant::group(); - $qillNames = array(); + $qillNames = []; foreach ($targetGroup[2] as $groupId) { if (array_key_exists($groupId, $groupNames)) { $qillNames[] = $groupNames[$groupId]; } } if (!empty($relationType)) { - $this->_qill[$grouping][] = $allRelationshipType[$relationType[2]] . " ( " . implode(", ", $qillNames) . " )"; + $relQill = ''; + foreach ($relTypes as $rel) { + if (!empty($relQill)) { + $relQill .= ' OR '; + } + $relQill .= CRM_Utils_Array::value($rel, $allRelationshipType); + } + $this->_qill[$grouping][] = 'Relationship Type(s) ' . $relQill . " ( " . implode(", ", $qillNames) . " )"; } else { $this->_qill[$grouping][] = implode(", ", $qillNames); } } + // Description + if (!empty($description[2]) && trim($description[2])) { + $this->_qill[$grouping][] = ts('Relationship description - ' . $description[2]); + $description = CRM_Core_DAO::escapeString(trim($description[2])); + $where[$grouping][] = "civicrm_relationship.description LIKE '%{$description}%'"; + } + // Note we do not currently set mySql to handle timezones, so doing this the old-fashioned way $today = date('Ymd'); //check for active, inactive and all relation status @@ -4109,38 +4306,22 @@ public function relationship(&$values) { } $onlyDeleted = 0; - if (in_array(array('deleted_contacts', '=', '1', '0', '0'), $this->_params)) { + if (in_array(['deleted_contacts', '=', '1', '0', '0'], $this->_params)) { $onlyDeleted = 1; } $where[$grouping][] = "(contact_b.is_deleted = {$onlyDeleted})"; - //check for permissioned, non-permissioned and all permissioned relations - if ($relPermission[2] == 1) { - $where[$grouping][] = "( -civicrm_relationship.is_permission_a_b = 1 -)"; - $this->_qill[$grouping][] = ts('Relationship - Permissioned'); - } - elseif ($relPermission[2] == 2) { - //non-allowed permission relationship. - $where[$grouping][] = "( -civicrm_relationship.is_permission_a_b = 0 -)"; - $this->_qill[$grouping][] = ts('Relationship - Non-permissioned'); - } - + $this->addRelationshipPermissionClauses($grouping, $where); $this->addRelationshipDateClauses($grouping, $where); - if (!empty($relationType) && !empty($rType) && isset($rType->id)) { - $where[$grouping][] = 'civicrm_relationship.relationship_type_id = ' . $rType->id; + $this->addRelationshipActivePeriodClauses($grouping, $where); + if (!empty($relTypes)) { + $where[$grouping][] = 'civicrm_relationship.relationship_type_id IN (' . implode(',', $relTypesIds) . ')'; } $this->_tables['civicrm_relationship'] = $this->_whereTables['civicrm_relationship'] = 1; $this->_useDistinct = TRUE; $this->_relationshipValuesAdded = TRUE; // it could be a or b, using an OR creates an unindexed join - better to create a temp table & // join on that, - // @todo creating a temp table could be expanded to group filter - // as even creating a temp table of all relationships is much much more efficient than - // an OR in the join if ($relationshipTempTable) { $whereClause = ''; if (!empty($where[$grouping])) { @@ -4149,34 +4330,62 @@ public function relationship(&$values) { } $sql = " CREATE TEMPORARY TABLE {$relationshipTempTable} - (SELECT contact_id_b as contact_id, civicrm_relationship.id + ( + `contact_id` int(10) unsigned NOT NULL DEFAULT '0', + `contact_id_alt` int(10) unsigned NOT NULL DEFAULT '0', + KEY `contact_id` (`contact_id`), + KEY `contact_id_alt` (`contact_id_alt`) + ) + (SELECT contact_id_b as contact_id, contact_id_a as contact_id_alt, civicrm_relationship.id FROM civicrm_relationship INNER JOIN civicrm_contact c ON civicrm_relationship.contact_id_a = c.id $whereClause ) UNION - (SELECT contact_id_a as contact_id, civicrm_relationship.id + (SELECT contact_id_a as contact_id, contact_id_b as contact_id_alt, civicrm_relationship.id FROM civicrm_relationship INNER JOIN civicrm_contact c ON civicrm_relationship.contact_id_b = c.id $whereClause ) "; CRM_Core_DAO::executeQuery($sql); } - } /** - * Add start & end date criteria in + * Add relationship permission criteria to where clause. + * * @param string $grouping - * @param array $where + * @param array $where Array to add "where" criteria to, in case you are generating a temp table. + * Not the main query. + */ + public function addRelationshipPermissionClauses($grouping, &$where) { + $relPermission = $this->getWhereValues('relation_permission', $grouping); + if ($relPermission) { + if (!is_array($relPermission[2])) { + // this form value was scalar in previous versions of Civi + $relPermission[2] = [$relPermission[2]]; + } + $where[$grouping][] = "(civicrm_relationship.is_permission_a_b IN (" . implode(",", $relPermission[2]) . "))"; + + $allRelationshipPermissions = CRM_Contact_BAO_Relationship::buildOptions('is_permission_a_b'); + + $relPermNames = array_intersect_key($allRelationshipPermissions, array_flip($relPermission[2])); + $this->_qill[$grouping][] = ts('Permissioned Relationships') . ' - ' . implode(' OR ', $relPermNames); + } + } + + /** + * Add start & end date criteria in + * @param string $grouping + * @param array $where * = array to add where clauses to, in case you are generating a temp table. * not the main query. */ public function addRelationshipDateClauses($grouping, &$where) { - $dateValues = array(); - $dateTypes = array( + $dateValues = []; + $dateTypes = [ 'start_date', 'end_date', - ); + ]; foreach ($dateTypes as $dateField) { $dateValueLow = $this->getWhereValues('relation_' . $dateField . '_low', $grouping); @@ -4194,6 +4403,64 @@ public function addRelationshipDateClauses($grouping, &$where) { } } + /** + * Add start & end active period criteria in + * @param string $grouping + * @param array $where + * = array to add where clauses to, in case you are generating a temp table. + * not the main query. + */ + public function addRelationshipActivePeriodClauses($grouping, &$where) { + $dateValues = []; + $dateField = 'active_period_date'; + + $dateValueLow = $this->getWhereValues('relation_active_period_date_low', $grouping); + $dateValueHigh = $this->getWhereValues('relation_active_period_date_high', $grouping); + $dateValueLowFormated = $dateValueHighFormated = NULL; + if (!empty($dateValueLow) && !empty($dateValueHigh)) { + $dateValueLowFormated = date('Ymd', strtotime($dateValueLow[2])); + $dateValueHighFormated = date('Ymd', strtotime($dateValueHigh[2])); + $this->_qill[$grouping][] = (ts('Relationship was active between')) . " " . CRM_Utils_Date::customFormat($dateValueLowFormated) . " and " . CRM_Utils_Date::customFormat($dateValueHighFormated); + } + elseif (!empty($dateValueLow)) { + $dateValueLowFormated = date('Ymd', strtotime($dateValueLow[2])); + $this->_qill[$grouping][] = (ts('Relationship was active after')) . " " . CRM_Utils_Date::customFormat($dateValueLowFormated); + } + elseif (!empty($dateValueHigh)) { + $dateValueHighFormated = date('Ymd', strtotime($dateValueHigh[2])); + $this->_qill[$grouping][] = (ts('Relationship was active before')) . " " . CRM_Utils_Date::customFormat($dateValueHighFormated); + } + + if ($activePeriodClauses = self::getRelationshipActivePeriodClauses($dateValueLowFormated, $dateValueHighFormated, TRUE)) { + $where[$grouping][] = $activePeriodClauses; + } + } + + /** + * Get start & end active period criteria + */ + public static function getRelationshipActivePeriodClauses($from, $to, $forceTableName) { + $tableName = $forceTableName ? 'civicrm_relationship.' : ''; + if (!is_null($from) && !is_null($to)) { + return '(((' . $tableName . 'start_date >= ' . $from . ' AND ' . $tableName . 'start_date <= ' . $to . ') OR + (' . $tableName . 'end_date >= ' . $from . ' AND ' . $tableName . 'end_date <= ' . $to . ') OR + (' . $tableName . 'start_date <= ' . $from . ' AND ' . $tableName . 'end_date >= ' . $to . ' )) OR + (' . $tableName . 'start_date IS NULL AND ' . $tableName . 'end_date IS NULL) OR + (' . $tableName . 'start_date IS NULL AND ' . $tableName . 'end_date >= ' . $from . ') OR + (' . $tableName . 'end_date IS NULL AND ' . $tableName . 'start_date <= ' . $to . '))'; + } + elseif (!is_null($from)) { + return '((' . $tableName . 'start_date >= ' . $from . ') OR + (' . $tableName . 'start_date IS NULL AND ' . $tableName . 'end_date IS NULL) OR + (' . $tableName . 'start_date IS NULL AND ' . $tableName . 'end_date >= ' . $from . '))'; + } + elseif (!is_null($to)) { + return '((' . $tableName . 'start_date <= ' . $to . ') OR + (' . $tableName . 'start_date IS NULL AND ' . $tableName . 'end_date IS NULL) OR + (' . $tableName . 'end_date IS NULL AND ' . $tableName . 'start_date <= ' . $to . '))'; + } + } + /** * Default set of return properties. * @@ -4204,7 +4471,7 @@ public function addRelationshipDateClauses($grouping, &$where) { */ public static function &defaultReturnProperties($mode = 1) { if (!isset(self::$_defaultReturnProperties)) { - self::$_defaultReturnProperties = array(); + self::$_defaultReturnProperties = []; } if (!isset(self::$_defaultReturnProperties[$mode])) { @@ -4217,7 +4484,7 @@ public static function &defaultReturnProperties($mode = 1) { } if (empty(self::$_defaultReturnProperties[$mode])) { - self::$_defaultReturnProperties[$mode] = array( + self::$_defaultReturnProperties[$mode] = [ 'home_URL' => 1, 'image_URL' => 1, 'legal_identifier' => 1, @@ -4271,7 +4538,7 @@ public static function &defaultReturnProperties($mode = 1) { 'contact_is_deleted' => 1, 'preferred_communication_method' => 1, 'preferred_language' => 1, - ); + ]; } } return self::$_defaultReturnProperties[$mode]; @@ -4337,6 +4604,7 @@ public static function getQuery($params = NULL, $returnProperties = NULL, $count * The api entity being called. * This sort-of duplicates $mode in a confusing way. Probably not by design. * + * @param bool|null $primaryLocationOnly * @return array */ public static function apiQuery( @@ -4350,7 +4618,8 @@ public static function apiQuery( $count = FALSE, $skipPermissions = TRUE, $mode = CRM_Contact_BAO_Query::MODE_CONTACTS, - $apiEntity = NULL + $apiEntity = NULL, + $primaryLocationOnly = NULL ) { $query = new CRM_Contact_BAO_Query( @@ -4359,7 +4628,7 @@ public static function apiQuery( $skipPermissions, TRUE, $smartGroupCache, NULL, 'AND', - $apiEntity + $apiEntity, $primaryLocationOnly ); //this should add a check for view deleted if permissions are enabled @@ -4385,8 +4654,9 @@ public static function apiQuery( $sql = "$select $from $where $having"; - // add group by - if ($query->_useGroupBy) { + // add group by only when API action is not getcount + // otherwise query fetches incorrect count + if ($query->_useGroupBy && !$count) { $sql .= self::getGroupByFromSelectColumns($query->_select, 'contact_a.id'); } if (!empty($sort)) { @@ -4404,12 +4674,11 @@ public static function apiQuery( // @todo derive this from the component class rather than hard-code two options. $entityIDField = ($mode == CRM_Contact_BAO_Query::MODE_CONTRIBUTE) ? 'contribution_id' : 'contact_id'; - $values = array(); + $values = []; while ($dao->fetch()) { if ($count) { $noRows = $dao->rowCount; - $dao->free(); - return array($noRows, NULL); + return [$noRows, NULL]; } $val = $query->store($dao); $convertedVals = $query->convertToPseudoNames($dao, TRUE, TRUE); @@ -4419,8 +4688,7 @@ public static function apiQuery( } $values[$dao->$entityIDField] = $val; } - $dao->free(); - return array($values, $options); + return [$values, $options]; } /** @@ -4491,22 +4759,22 @@ protected static function convertCustomRelativeFields(&$formValues, &$params, $v if ($from) { if ($to) { - $relativeFunction = array('BETWEEN' => array($from, $to)); + $relativeFunction = ['BETWEEN' => [$from, $to]]; } else { - $relativeFunction = array('>=' => $from); + $relativeFunction = ['>=' => $from]; } } else { - $relativeFunction = array('<=' => $to); + $relativeFunction = ['<=' => $to]; } - $params[] = array( + $params[] = [ $customFieldName, '=', $relativeFunction, 0, 0, - ); + ]; } /** @@ -4520,7 +4788,7 @@ public static function isCustomDateField($fieldName) { if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($fieldName)) == FALSE) { return FALSE; } - if ('Date' == civicrm_api3('CustomField', 'getvalue', array('id' => $customFieldID, 'return' => 'data_type'))) { + if ('Date' == civicrm_api3('CustomField', 'getvalue', ['id' => $customFieldID, 'return' => 'data_type'])) { return TRUE; } return FALSE; @@ -4580,27 +4848,21 @@ public static function filterCountryFromValuesIfStateExists(&$formValues) { * * @param array $selectClauses * @param array $groupBy - Columns already included in GROUP By clause. + * @param string $aggregateFunction * * @return string */ - public static function appendAnyValueToSelect($selectClauses, $groupBy) { - $mysqlVersion = CRM_Core_DAO::singleValueQuery('SELECT VERSION()'); - $sqlMode = explode(',', CRM_Core_DAO::singleValueQuery('SELECT @@sql_mode')); - - // Disable only_full_group_by mode for lower sql versions. - if (version_compare($mysqlVersion, '5.7', '<') || (!empty($sqlMode) && !in_array('ONLY_FULL_GROUP_BY', $sqlMode))) { - $key = array_search('ONLY_FULL_GROUP_BY', $sqlMode); - unset($sqlMode[$key]); - CRM_Core_DAO::executeQuery("SET SESSION sql_mode = '" . implode(',', $sqlMode) . "'"); - } - else { + public static function appendAnyValueToSelect($selectClauses, $groupBy, $aggregateFunction = 'ANY_VALUE') { + if (!CRM_Utils_SQL::disableFullGroupByMode()) { $groupBy = array_map('trim', (array) $groupBy); - $aggregateFunctions = '/(ROUND|AVG|COUNT|GROUP_CONCAT|SUM|MAX|MIN)\(/i'; + $aggregateFunctions = '/(ROUND|AVG|COUNT|GROUP_CONCAT|SUM|MAX|MIN|IF)[[:blank:]]*\(/i'; foreach ($selectClauses as $key => &$val) { list($selectColumn, $alias) = array_pad(preg_split('/ as /i', $val), 2, NULL); // append ANY_VALUE() keyword if (!in_array($selectColumn, $groupBy) && preg_match($aggregateFunctions, trim($selectColumn)) !== 1) { - $val = str_replace($selectColumn, "ANY_VALUE({$selectColumn})", $val); + $val = ($aggregateFunction == 'GROUP_CONCAT') ? + str_replace($selectColumn, "$aggregateFunction(DISTINCT {$selectColumn})", $val) : + str_replace($selectColumn, "$aggregateFunction({$selectColumn})", $val); } } } @@ -4608,6 +4870,28 @@ public static function appendAnyValueToSelect($selectClauses, $groupBy) { return "SELECT " . implode(', ', $selectClauses) . " "; } + /** + * For some special cases, where if non-aggregate ORDER BY columns are not present in GROUP BY + * on full_group_by mode, then append the those missing columns to GROUP BY clause + * keyword to select fields not present in groupBy + * + * @param string $groupBy - GROUP BY clause where missing ORDER BY columns will be appended if not present + * @param array $orderBys - ORDER BY sub-clauses + * + */ + public static function getGroupByFromOrderBy(&$groupBy, $orderBys) { + if (!CRM_Utils_SQL::disableFullGroupByMode()) { + foreach ($orderBys as $orderBy) { + // remove sort syntax from ORDER BY clauses if present + $orderBy = str_ireplace([' DESC', ' ASC', '`'], '', $orderBy); + // if ORDER BY column is not present in GROUP BY then append it to end + if (preg_match('/(MAX|MIN)\(/i', trim($orderBy)) !== 1 && !strstr($groupBy, $orderBy)) { + $groupBy .= ", {$orderBy}"; + } + } + } + } + /** * Include Select columns in groupBy clause. * @@ -4622,8 +4906,8 @@ public static function getGroupByFromSelectColumns($selectClauses, $groupBy = NU $sqlMode = CRM_Core_DAO::singleValueQuery('SELECT @@sql_mode'); //return if ONLY_FULL_GROUP_BY is not enabled. - if (!version_compare($mysqlVersion, '5.7', '<') && !empty($sqlMode) && in_array('ONLY_FULL_GROUP_BY', explode(',', $sqlMode))) { - $regexToExclude = '/(ROUND|AVG|COUNT|GROUP_CONCAT|SUM|MAX|MIN)\(/i'; + if (CRM_Utils_SQL::supportsFullGroupBy() && !empty($sqlMode) && in_array('ONLY_FULL_GROUP_BY', explode(',', $sqlMode))) { + $regexToExclude = '/(ROUND|AVG|COUNT|GROUP_CONCAT|SUM|MAX|MIN|IF)[[:blank:]]*\(/i'; foreach ($selectClauses as $key => $val) { $aliasArray = preg_split('/ as /i', $val); // if more than 1 alias we need to split by ','. @@ -4699,89 +4983,7 @@ public function searchQuery( $additionalFromClause = NULL, $skipOrderAndLimit = FALSE ) { - if ($includeContactIds) { - $this->_includeContactIds = TRUE; - $this->_whereClause = $this->whereClause(); - } - - $onlyDeleted = in_array(array('deleted_contacts', '=', '1', '0', '0'), $this->_params); - - // if we’re explicitly looking for a certain contact’s contribs, events, etc. - // and that contact happens to be deleted, set $onlyDeleted to true - foreach ($this->_params as $values) { - $name = CRM_Utils_Array::value(0, $values); - $op = CRM_Utils_Array::value(1, $values); - $value = CRM_Utils_Array::value(2, $values); - if ($name == 'contact_id' and $op == '=') { - if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'is_deleted')) { - $onlyDeleted = TRUE; - } - break; - } - } - - // building the query string - $groupBy = $groupByCol = NULL; - if (!$count) { - if (isset($this->_groupByComponentClause)) { - $groupBy = $this->_groupByComponentClause; - $groupCols = preg_replace('/^GROUP BY /', '', trim($this->_groupByComponentClause)); - $groupByCol = explode(', ', $groupCols); - } - elseif ($this->_useGroupBy) { - $groupByCol = 'contact_a.id'; - $groupBy = ' GROUP BY contact_a.id'; - } - } - if ($this->_mode & CRM_Contact_BAO_Query::MODE_ACTIVITY && (!$count)) { - $groupByCol = 'civicrm_activity.id'; - $groupBy = 'GROUP BY civicrm_activity.id '; - } - - $order = $orderBy = $limit = ''; - if (!$count) { - list($order, $additionalFromClause) = $this->prepareOrderBy($sort, $sortByChar, $sortOrder, $additionalFromClause); - - if ($rowCount > 0 && $offset >= 0) { - $offset = CRM_Utils_Type::escape($offset, 'Int'); - $rowCount = CRM_Utils_Type::escape($rowCount, 'Int'); - $limit = " LIMIT $offset, $rowCount "; - } - } - - // CRM-15231 - $this->_sort = $sort; - - //CRM-15967 - $this->includePseudoFieldsJoin($sort); - - list($select, $from, $where, $having) = $this->query($count, $sortByChar, $groupContacts, $onlyDeleted); - - if (!empty($groupByCol)) { - $groupBy = self::getGroupByFromSelectColumns($this->_select, $groupByCol); - } - - if ($additionalWhereClause) { - $where = $where . ' AND ' . $additionalWhereClause; - } - - //additional from clause should be w/ proper joins. - if ($additionalFromClause) { - $from .= "\n" . $additionalFromClause; - } - - // if we are doing a transform, do it here - // use the $from, $where and $having to get the contact ID - if ($this->_displayRelationshipType) { - $this->filterRelatedContacts($from, $where, $having); - } - - if ($skipOrderAndLimit) { - $query = "$select $from $where $having $groupBy"; - } - else { - $query = "$select $from $where $having $groupBy $order $limit"; - } + $query = $this->getSearchSQL($offset, $rowCount, $sort, $count, $includeContactIds, $sortByChar, $groupContacts, $additionalWhereClause, $sortOrder, $additionalFromClause, $skipOrderAndLimit); if ($returnQuery) { return $query; @@ -4791,12 +4993,15 @@ public function searchQuery( } $dao = CRM_Core_DAO::executeQuery($query); + + // We can always call this - it will only re-enable if it was originally enabled. + CRM_Core_DAO::reenableFullGroupByMode(); + if ($groupContacts) { - $ids = array(); + $ids = []; while ($dao->fetch()) { $ids[] = $dao->id; } - $dao->free(); return implode(',', $ids); } @@ -4804,26 +5009,65 @@ public function searchQuery( } /** - * Fetch a list of contacts from the prev/next cache for displaying a search results page + * Create and query the db for the list of all first letters used by contacts * - * @param string $cacheKey - * @param int $offset - * @param int $rowCount + * @return CRM_Core_DAO + */ + public function alphabetQuery() { + $sqlParts = $this->getSearchSQLParts(NULL, NULL, NULL, FALSE, FALSE, TRUE); + $query = "SELECT DISTINCT LEFT(contact_a.sort_name, 1) as sort_name + {$sqlParts['from']} + {$sqlParts['where']} + {$sqlParts['having']} + GROUP BY sort_name + ORDER BY sort_name asc"; + $dao = CRM_Core_DAO::executeQuery($query); + return $dao; + } + + /** + * Fetch a list of contacts for displaying a search results page + * + * @param array $cids + * List of contact IDs * @param bool $includeContactIds * @return CRM_Core_DAO */ - public function getCachedContacts($cacheKey, $offset, $rowCount, $includeContactIds) { + public function getCachedContacts($cids, $includeContactIds) { + CRM_Core_DAO::disableFullGroupByMode(); + CRM_Utils_Type::validateAll($cids, 'Positive'); $this->_includeContactIds = $includeContactIds; - $onlyDeleted = in_array(array('deleted_contacts', '=', '1', '0', '0'), $this->_params); + $onlyDeleted = in_array(['deleted_contacts', '=', '1', '0', '0'], $this->_params); list($select, $from, $where) = $this->query(FALSE, FALSE, FALSE, $onlyDeleted); - $from = " FROM civicrm_prevnext_cache pnc INNER JOIN civicrm_contact contact_a ON contact_a.id = pnc.entity_id1 AND pnc.cacheKey = '$cacheKey' " . substr($from, 31); - $order = " ORDER BY pnc.id"; - $groupByCol = array('contact_a.id', 'pnc.id'); - $groupBy = self::getGroupByFromSelectColumns($this->_select, $groupByCol); - $limit = " LIMIT $offset, $rowCount"; + $select .= sprintf(", (%s) AS _wgt", $this->createSqlCase('contact_a.id', $cids)); + $where .= sprintf(' AND contact_a.id IN (%s)', implode(',', $cids)); + $order = 'ORDER BY _wgt'; + $groupBy = $this->_useGroupBy ? ' GROUP BY contact_a.id' : ''; + $limit = ''; $query = "$select $from $where $groupBy $order $limit"; - return CRM_Core_DAO::executeQuery($query); + $result = CRM_Core_DAO::executeQuery($query); + CRM_Core_DAO::reenableFullGroupByMode(); + return $result; + } + + /** + * Construct a SQL CASE expression. + * + * @param string $idCol + * The name of a column with ID's (eg 'contact_a.id'). + * @param array $cids + * Array(int $weight => int $id). + * @return string + * CASE WHEN id=123 THEN 1 WHEN id=456 THEN 2 END + */ + private function createSqlCase($idCol, $cids) { + $buf = "CASE\n"; + foreach ($cids as $weight => $cid) { + $buf .= " WHEN $idCol = $cid THEN $weight \n"; + } + $buf .= "END\n"; + return $buf; } /** @@ -4891,159 +5135,49 @@ public function setSkipPermission($val) { * * @return array */ - public function &summaryContribution($context = NULL) { + public function summaryContribution($context = NULL) { list($innerselect, $from, $where, $having) = $this->query(TRUE); - - // hack $select - $select = " -SELECT COUNT( conts.total_amount ) as total_count, - SUM( conts.total_amount ) as total_amount, - AVG( conts.total_amount ) as total_avg, - conts.currency as currency"; if ($this->_permissionWhereClause) { $where .= " AND " . $this->_permissionWhereClause; } if ($context == 'search') { $where .= " AND contact_a.is_deleted = 0 "; } - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); - if (!empty($financialTypes)) { - $where .= " AND civicrm_contribution.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ") AND li.id IS NULL"; - $from .= " LEFT JOIN civicrm_line_item li - ON civicrm_contribution.id = li.contribution_id AND - li.entity_table = 'civicrm_contribution' AND li.financial_type_id NOT IN (" . implode(',', array_keys($financialTypes)) . ") "; - } - else { - $where .= " AND civicrm_contribution.financial_type_id IN (0)"; - } - - // make sure contribution is completed - CRM-4989 - $completedWhere = $where . " AND civicrm_contribution.contribution_status_id = 1 "; - - $summary = array(); - $summary['total'] = array(); - $summary['total']['count'] = $summary['total']['amount'] = $summary['total']['avg'] = "n/a"; - $innerQuery = "SELECT civicrm_contribution.total_amount, COUNT(civicrm_contribution.total_amount) as civicrm_contribution_total_amount_count, - civicrm_contribution.currency $from $completedWhere"; - $query = "$select FROM ( - $innerQuery GROUP BY civicrm_contribution.id - ) as conts - GROUP BY currency"; - - $dao = CRM_Core_DAO::executeQuery($query); - - $summary['total']['count'] = 0; - $summary['total']['amount'] = $summary['total']['avg'] = array(); - while ($dao->fetch()) { - $summary['total']['count'] += $dao->total_count; - $summary['total']['amount'][] = CRM_Utils_Money::format($dao->total_amount, $dao->currency); - $summary['total']['avg'][] = CRM_Utils_Money::format($dao->total_avg, $dao->currency); - } - - $orderBy = 'ORDER BY civicrm_contribution_total_amount_count DESC'; - $groupBy = 'GROUP BY currency, civicrm_contribution.total_amount'; - $modeSQL = "$select, SUBSTRING_INDEX(GROUP_CONCAT(conts.total_amount - ORDER BY conts.civicrm_contribution_total_amount_count DESC SEPARATOR ';'), ';', 1) as amount, - MAX(conts.civicrm_contribution_total_amount_count) as civicrm_contribution_total_amount_count - FROM ($innerQuery - $groupBy $orderBy) as conts - GROUP BY currency"; - - $summary['total']['mode'] = CRM_Contribute_BAO_Contribution::computeStats('mode', $modeSQL); - $medianSQL = "{$from} {$completedWhere}"; - $summary['total']['median'] = CRM_Contribute_BAO_Contribution::computeStats('median', $medianSQL, 'civicrm_contribution'); - $summary['total']['currencyCount'] = count($summary['total']['median']); + $this->appendFinancialTypeWhereAndFromToQueryStrings($where, $from); - if (!empty($summary['total']['amount'])) { - $summary['total']['amount'] = implode(', ', $summary['total']['amount']); - $summary['total']['avg'] = implode(', ', $summary['total']['avg']); - $summary['total']['mode'] = implode(', ', $summary['total']['mode']); - $summary['total']['median'] = implode(', ', $summary['total']['median']); - } - else { - $summary['total']['amount'] = $summary['total']['avg'] = $summary['total']['median'] = 0; - } + $summary = ['total' => []]; + $this->addBasicStatsToSummary($summary, $where, $from); - // soft credit summary if (CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled()) { - $softCreditWhere = "{$completedWhere} AND civicrm_contribution_soft.id IS NOT NULL"; - $query = " - $select FROM ( - SELECT civicrm_contribution_soft.amount as total_amount, civicrm_contribution_soft.currency $from $softCreditWhere - GROUP BY civicrm_contribution_soft.id - ) as conts - GROUP BY currency"; - $dao = CRM_Core_DAO::executeQuery($query); - $summary['soft_credit']['count'] = 0; - $summary['soft_credit']['amount'] = $summary['soft_credit']['avg'] = array(); - while ($dao->fetch()) { - $summary['soft_credit']['count'] += $dao->total_count; - $summary['soft_credit']['amount'][] = CRM_Utils_Money::format($dao->total_amount, $dao->currency); - $summary['soft_credit']['avg'][] = CRM_Utils_Money::format($dao->total_avg, $dao->currency); - } - if (!empty($summary['soft_credit']['amount'])) { - $summary['soft_credit']['amount'] = implode(', ', $summary['soft_credit']['amount']); - $summary['soft_credit']['avg'] = implode(', ', $summary['soft_credit']['avg']); - } - else { - $summary['soft_credit']['amount'] = $summary['soft_credit']['avg'] = 0; - } - } - - // hack $select - //@todo - this could be one query using the IF in mysql - eg - // SELECT sum(total_completed), sum(count_completed), sum(count_cancelled), sum(total_cancelled) FROM ( - // SELECT civicrm_contribution.total_amount, civicrm_contribution.currency , - // IF(civicrm_contribution.contribution_status_id = 1, 1, 0 ) as count_completed, - // IF(civicrm_contribution.contribution_status_id = 1, total_amount, 0 ) as total_completed, - // IF(civicrm_contribution.cancel_date IS NOT NULL = 1, 1, 0 ) as count_cancelled, - // IF(civicrm_contribution.cancel_date IS NOT NULL = 1, total_amount, 0 ) as total_cancelled - // FROM civicrm_contact contact_a - // LEFT JOIN civicrm_contribution ON civicrm_contribution.contact_id = contact_a.id - // WHERE ( ... where clause.... - // AND (civicrm_contribution.cancel_date IS NOT NULL OR civicrm_contribution.contribution_status_id = 1) - // ) as conts - - $select = " -SELECT COUNT( conts.total_amount ) as cancel_count, - SUM( conts.total_amount ) as cancel_amount, - AVG( conts.total_amount ) as cancel_avg, - conts.currency as currency"; - - $where .= " AND civicrm_contribution.cancel_date IS NOT NULL "; - if ($context == 'search') { - $where .= " AND contact_a.is_deleted = 0 "; + $this->addBasicSoftCreditStatsToStats($summary, $where, $from); } - $query = "$select FROM ( - SELECT civicrm_contribution.total_amount, civicrm_contribution.currency $from $where - GROUP BY civicrm_contribution.id - ) as conts - GROUP BY currency"; + $this->addBasicCancelStatsToSummary($summary, $where, $from); - $dao = CRM_Core_DAO::executeQuery($query); + return $summary; + } - if ($dao->N <= 1) { - if ($dao->fetch()) { - $summary['cancel']['count'] = $dao->cancel_count; - $summary['cancel']['amount'] = CRM_Utils_Money::format($dao->cancel_amount, $dao->currency); - $summary['cancel']['avg'] = CRM_Utils_Money::format($dao->cancel_avg, $dao->currency); - } + /** + * Append financial ACL limits to the query from & where clauses, if applicable. + * + * @param string $where + * @param string $from + */ + public function appendFinancialTypeWhereAndFromToQueryStrings(&$where, &$from) { + if (!CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()) { + return; + } + CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); + if (!empty($financialTypes)) { + $where .= " AND civicrm_contribution.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ") AND li.id IS NULL"; + $from .= " LEFT JOIN civicrm_line_item li + ON civicrm_contribution.id = li.contribution_id AND + li.entity_table = 'civicrm_contribution' AND li.financial_type_id NOT IN (" . implode(',', array_keys($financialTypes)) . ") "; } else { - $summary['cancel']['count'] = 0; - $summary['cancel']['amount'] = $summary['cancel']['avg'] = array(); - while ($dao->fetch()) { - $summary['cancel']['count'] += $dao->cancel_count; - $summary['cancel']['amount'][] = CRM_Utils_Money::format($dao->cancel_amount, $dao->currency); - $summary['cancel']['avg'][] = CRM_Utils_Money::format($dao->cancel_avg, $dao->currency); - } - $summary['cancel']['amount'] = implode(', ', $summary['cancel']['amount']); - $summary['cancel']['avg'] = implode(', ', $summary['cancel']['avg']); + $where .= " AND civicrm_contribution.financial_type_id IN (0)"; } - - return $summary; } /** @@ -5062,7 +5196,7 @@ public function qill() { */ public static function &defaultHierReturnProperties() { if (!isset(self::$_defaultHierReturnProperties)) { - self::$_defaultHierReturnProperties = array( + self::$_defaultHierReturnProperties = [ 'home_URL' => 1, 'image_URL' => 1, 'legal_identifier' => 1, @@ -5090,8 +5224,8 @@ public static function &defaultHierReturnProperties() { 'do_not_mail' => 1, 'do_not_sms' => 1, 'do_not_trade' => 1, - 'location' => array( - '1' => array( + 'location' => [ + '1' => [ 'location_type' => 1, 'street_address' => 1, 'city' => 1, @@ -5111,8 +5245,8 @@ public static function &defaultHierReturnProperties() { 'email-1' => 1, 'email-2' => 1, 'email-3' => 1, - ), - '2' => array( + ], + '2' => [ 'location_type' => 1, 'street_address' => 1, 'city' => 1, @@ -5131,9 +5265,9 @@ public static function &defaultHierReturnProperties() { 'email-1' => 1, 'email-2' => 1, 'email-3' => 1, - ), - ), - ); + ], + ], + ]; } return self::$_defaultHierReturnProperties; } @@ -5147,12 +5281,15 @@ public static function &defaultHierReturnProperties() { * @param string $dbFieldName * @param string $fieldTitle * @param bool $appendTimeStamp + * @param string $dateFormat */ public function dateQueryBuilder( - &$values, $tableName, $fieldName, + $values, $tableName, $fieldName, $dbFieldName, $fieldTitle, - $appendTimeStamp = TRUE + $appendTimeStamp = TRUE, + $dateFormat = 'YmdHis' ) { + // @todo - remove dateFormat - pretty sure it's never passed in... list($name, $op, $value, $grouping, $wildcard) = $values; if ($name == "{$fieldName}_low" || @@ -5168,7 +5305,7 @@ public function dateQueryBuilder( if ($name == $fieldName . '_low') { $firstOP = '>='; $firstPhrase = ts('greater than or equal to'); - $firstDate = CRM_Utils_Date::processDate($value); + $firstDate = CRM_Utils_Date::processDate($value, NULL, FALSE, $dateFormat); $secondValues = $this->getWhereValues("{$fieldName}_high", $grouping); if (!empty($secondValues) && $secondValues[2]) { @@ -5179,7 +5316,7 @@ public function dateQueryBuilder( if ($appendTimeStamp && strlen($secondValue) == 10) { $secondValue .= ' 23:59:59'; } - $secondDate = CRM_Utils_Date::processDate($secondValue); + $secondDate = CRM_Utils_Date::processDate($secondValue, NULL, FALSE, $dateFormat); } } elseif ($name == $fieldName . '_high') { @@ -5189,14 +5326,14 @@ public function dateQueryBuilder( if ($appendTimeStamp && strlen($value) == 10) { $value .= ' 23:59:59'; } - $firstDate = CRM_Utils_Date::processDate($value); + $firstDate = CRM_Utils_Date::processDate($value, NULL, FALSE, $dateFormat); $secondValues = $this->getWhereValues("{$fieldName}_low", $grouping); if (!empty($secondValues) && $secondValues[2]) { $secondOP = '>='; $secondPhrase = ts('greater than or equal to'); $secondValue = $secondValues[2]; - $secondDate = CRM_Utils_Date::processDate($secondValue); + $secondDate = CRM_Utils_Date::processDate($secondValue, NULL, FALSE, $dateFormat); } } @@ -5235,9 +5372,9 @@ public function dateQueryBuilder( $date = $format = NULL; if (strstr($op, 'IN')) { - $format = array(); + $format = []; foreach ($value as &$date) { - $date = CRM_Utils_Date::processDate($date); + $date = CRM_Utils_Date::processDate($date, NULL, FALSE, $dateFormat); if (!$appendTimeStamp) { $date = substr($date, 0, 8); } @@ -5247,7 +5384,7 @@ public function dateQueryBuilder( $format = implode(', ', $format); } elseif ($value && (!strstr($op, 'NULL') && !strstr($op, 'EMPTY'))) { - $date = CRM_Utils_Date::processDate($value); + $date = CRM_Utils_Date::processDate($value, NULL, FALSE, $dateFormat); if (!$appendTimeStamp) { $date = substr($date, 0, 8); } @@ -5351,7 +5488,6 @@ public function numberRangeBuilder( } } - /** * @param $values * @param string $tableName @@ -5369,7 +5505,8 @@ public function ageRangeQueryBuilder( list($name, $op, $value, $grouping, $wildcard) = $values; $asofDateValues = $this->getWhereValues("{$fieldName}_asof_date", $grouping); - $asofDate = NULL; // will be treated as current day + // will be treated as current day + $asofDate = NULL; if ($asofDateValues) { $asofDate = CRM_Utils_Date::processDate($asofDateValues[2]); $asofDateFormat = CRM_Utils_Date::customFormat(substr($asofDate, 0, 8)); @@ -5485,22 +5622,25 @@ public static function buildClause($field, $op, $value = NULL, $dataType = NULL) return $clause; case 'IS EMPTY': - $clause = " (NULLIF($field, '') IS NULL) "; + $clause = ($dataType == 'Date') ? " $field IS NULL " : " (NULLIF($field, '') IS NULL) "; return $clause; case 'IS NOT EMPTY': - $clause = " (NULLIF($field, '') IS NOT NULL) "; + $clause = ($dataType == 'Date') ? " $field IS NOT NULL " : " (NULLIF($field, '') IS NOT NULL) "; return $clause; + case 'RLIKE': + return " {$clause} BINARY '{$value}' "; + case 'IN': case 'NOT IN': // I feel like this would be escaped properly if passed through $queryString = CRM_Core_DAO::createSqlFilter. if (!empty($value) && (!is_array($value) || !array_key_exists($op, $value))) { - $value = array($op => (array) $value); + $value = [$op => (array) $value]; } default: - if (empty($dataType)) { + if (empty($dataType) || $dataType == 'Date') { $dataType = 'String'; } if (is_array($value)) { @@ -5511,32 +5651,19 @@ public static function buildClause($field, $op, $value = NULL, $dataType = NULL) return $queryString; } - - // This is the here-be-dragons zone. We have no other hopes left for an array so lets assume it 'should' be array('IN' => array(2,5)) - // but we got only array(2,5) from the form. - // We could get away with keeping this in 4.6 if we make it such that it throws an enotice in 4.7 so - // people have to de-slopify it. - if (!empty($value[0])) { - if ($op != 'BETWEEN') { - $dragonPlace = $iAmAnIntentionalENoticeThatWarnsOfAProblemYouShouldReport; - } - if (($queryString = CRM_Core_DAO::createSqlFilter($field, array($op => $value), $dataType)) != FALSE) { - return $queryString; - } - } - else { - $op = 'IN'; - $dragonPlace = $iAmAnIntentionalENoticeThatWarnsOfAProblemYouShouldReportUsingOldFormat; - if (($queryString = CRM_Core_DAO::createSqlFilter($field, array($op => array_keys($value)), $dataType)) != FALSE) { + if (!empty($value[0]) && $op === 'BETWEEN') { + CRM_Core_Error::deprecatedFunctionWarning('Fix search input params'); + if (($queryString = CRM_Core_DAO::createSqlFilter($field, [$op => $value], $dataType)) != FALSE) { return $queryString; } } + throw new CRM_Core_Exception(ts('Failed to interpret input for search')); } $value = CRM_Utils_Type::escape($value, $dataType); // if we don't have a dataType we should assume if ($dataType == 'String' || $dataType == 'Text') { - $value = "'" . strtolower($value) . "'"; + $value = "'" . $value . "'"; } return "$clause $value"; } @@ -5553,7 +5680,7 @@ public function openedSearchPanes($reset = FALSE) { } // pane name to table mapper - $panesMapper = array( + $panesMapper = [ ts('Contributions') => 'civicrm_contribution', ts('Memberships') => 'civicrm_membership', ts('Events') => 'civicrm_participant', @@ -5566,7 +5693,7 @@ public function openedSearchPanes($reset = FALSE) { ts('Notes') => 'civicrm_note', ts('Change Log') => 'civicrm_log', ts('Mailings') => 'civicrm_mailing', - ); + ]; CRM_Contact_BAO_Query_Hook::singleton()->getPanesMapper($panesMapper); foreach (array_keys($this->_whereTables) as $table) { @@ -5582,7 +5709,7 @@ public function openedSearchPanes($reset = FALSE) { * @param $operator */ public function setOperator($operator) { - $validOperators = array('AND', 'OR'); + $validOperators = ['AND', 'OR']; if (!in_array($operator, $validOperators)) { $operator = 'AND'; } @@ -5602,17 +5729,28 @@ public function getOperator() { * @param $having */ public function filterRelatedContacts(&$from, &$where, &$having) { - static $_rTypeProcessed = NULL; - static $_rTypeFrom = NULL; - static $_rTypeWhere = NULL; - - if (!$_rTypeProcessed) { - $_rTypeProcessed = TRUE; - + if (!isset(Civi::$statics[__CLASS__]['related_contacts_filter'])) { + Civi::$statics[__CLASS__]['related_contacts_filter'] = []; + } + $_rTempCache =& Civi::$statics[__CLASS__]['related_contacts_filter']; + // since there only can be one instance of this filter in every query + // skip if filter has already applied + foreach ($_rTempCache as $acache) { + foreach ($acache['queries'] as $aqcache) { + if (strpos($from, $aqcache['from']) !== FALSE) { + $having = NULL; + return; + } + } + } + $arg_sig = sha1("$from $where $having"); + if (isset($_rTempCache[$arg_sig])) { + $cache = $_rTempCache[$arg_sig]; + } + else { // create temp table with contact ids - $tableName = CRM_Core_DAO::createTempTableName('civicrm_transform', TRUE); - $sql = "CREATE TEMPORARY TABLE $tableName ( contact_id int primary key) ENGINE=HEAP"; - CRM_Core_DAO::executeQuery($sql); + + $tableName = CRM_Utils_SQL_TempTable::build()->createWithColumns('contact_id int primary key')->setMemory(TRUE)->getName(); $sql = " REPLACE INTO $tableName ( contact_id ) @@ -5623,16 +5761,27 @@ public function filterRelatedContacts(&$from, &$where, &$having) { "; CRM_Core_DAO::executeQuery($sql); - $qillMessage = ts('Contacts with a Relationship Type of: '); + $cache = ['tableName' => $tableName, 'queries' => []]; + $_rTempCache[$arg_sig] = $cache; + } + // upsert the query depending on relationship type + if (isset($cache['queries'][$this->_displayRelationshipType])) { + $qcache = $cache['queries'][$this->_displayRelationshipType]; + } + else { + $tableName = $cache['tableName']; + $qcache = [ + "from" => "", + "where" => "", + ]; $rTypes = CRM_Core_PseudoConstant::relationshipType(); - if (is_numeric($this->_displayRelationshipType)) { $relationshipTypeLabel = $rTypes[$this->_displayRelationshipType]['label_a_b']; - $_rTypeFrom = " + $qcache['from'] = " INNER JOIN civicrm_relationship displayRelType ON ( displayRelType.contact_id_a = contact_a.id OR displayRelType.contact_id_b = contact_a.id ) INNER JOIN $tableName transform_temp ON ( transform_temp.contact_id = displayRelType.contact_id_a OR transform_temp.contact_id = displayRelType.contact_id_b ) "; - $_rTypeWhere = " + $qcache['where'] = " WHERE displayRelType.relationship_type_id = {$this->_displayRelationshipType} AND displayRelType.is_active = 1 "; @@ -5641,36 +5790,40 @@ public function filterRelatedContacts(&$from, &$where, &$having) { list($relType, $dirOne, $dirTwo) = explode('_', $this->_displayRelationshipType); if ($dirOne == 'a') { $relationshipTypeLabel = $rTypes[$relType]['label_a_b']; - $_rTypeFrom .= " + $qcache['from'] .= " INNER JOIN civicrm_relationship displayRelType ON ( displayRelType.contact_id_a = contact_a.id ) INNER JOIN $tableName transform_temp ON ( transform_temp.contact_id = displayRelType.contact_id_b ) "; } else { $relationshipTypeLabel = $rTypes[$relType]['label_b_a']; - $_rTypeFrom .= " + $qcache['from'] .= " INNER JOIN civicrm_relationship displayRelType ON ( displayRelType.contact_id_b = contact_a.id ) INNER JOIN $tableName transform_temp ON ( transform_temp.contact_id = displayRelType.contact_id_a ) "; } - $_rTypeWhere = " + $qcache['where'] = " WHERE displayRelType.relationship_type_id = $relType AND displayRelType.is_active = 1 "; } - $this->_qill[0][] = $qillMessage . "'" . $relationshipTypeLabel . "'"; + $qcache['relTypeLabel'] = $relationshipTypeLabel; + $_rTempCache[$arg_sig]['queries'][$this->_displayRelationshipType] = $qcache; } - - if (!empty($this->_permissionWhereClause)) { - $_rTypeWhere .= "AND $this->_permissionWhereClause"; + $qillMessage = ts('Contacts with a Relationship Type of: '); + $iqill = $qillMessage . "'" . $qcache['relTypeLabel'] . "'"; + if (!is_array($this->_qill[0]) || !in_array($iqill, $this->_qill[0])) { + $this->_qill[0][] = $iqill; } - - if (strpos($from, $_rTypeFrom) === FALSE) { + if (strpos($from, $qcache['from']) === FALSE) { // lets replace all the INNER JOIN's in the $from so we dont exclude other data // this happens when we have an event_type in the quert (CRM-7969) $from = str_replace("INNER JOIN", "LEFT JOIN", $from); - $from .= $_rTypeFrom; - $where = $_rTypeWhere; + $from .= $qcache['from']; + $where = $qcache['where']; + if (!empty($this->_permissionWhereClause)) { + $where .= "AND $this->_permissionWhereClause"; + } } $having = NULL; @@ -5684,8 +5837,7 @@ public function filterRelatedContacts(&$from, &$where, &$having) { * @return bool */ public static function caseImportant($op) { - return - in_array($op, array('LIKE', 'IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY')) ? FALSE : TRUE; + return in_array($op, ['LIKE', 'IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY']) ? FALSE : TRUE; } /** @@ -5736,7 +5888,7 @@ public function optionValueQuery( $useIDsOnly = FALSE ) { - $pseudoFields = array( + $pseudoFields = [ 'email_greeting', 'postal_greeting', 'addressee', @@ -5744,7 +5896,7 @@ public function optionValueQuery( 'prefix_id', 'suffix_id', 'communication_style_id', - ); + ]; if ($useIDsOnly) { list($tableName, $fieldName) = explode('.', $field['where'], 2); @@ -5752,15 +5904,23 @@ public function optionValueQuery( $wc = "contact_a.$fieldName"; } else { - $wc = "$tableName.id"; + // Special handling for on_hold, so that we actually use the 'where' + // property in order to limit the query by the on_hold status of the email, + // instead of using email.id which would be nonsensical. + if ($field['name'] == 'on_hold') { + $wc = "{$field['where']}"; + } + else { + $wc = "$tableName.id"; + } } } else { - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = self::caseImportant($op) ? "LOWER({$field['where']})" : "{$field['where']}"; + CRM_Core_Error::deprecatedFunctionWarning('pass $ids to this method'); + $wc = "{$field['where']}"; } if (in_array($name, $pseudoFields)) { - if (!in_array($name, array('gender_id', 'prefix_id', 'suffix_id', 'communication_style_id'))) { + if (!in_array($name, ['gender_id', 'prefix_id', 'suffix_id', 'communication_style_id'])) { $wc = "contact_a.{$name}_id"; } $dataType = 'Positive'; @@ -5771,7 +5931,7 @@ public function optionValueQuery( } list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue($daoName, $field['name'], $value, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $label, 2 => $qillop, 3 => $qillVal)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $label, 2 => $qillop, 3 => $qillVal]); $this->_where[$grouping][] = self::buildClause($wc, $op, $value, $dataType); } @@ -5791,7 +5951,7 @@ public function optionValueQuery( public static function parseSearchBuilderString($string, $dataType = 'Integer') { $string = trim($string); if (substr($string, 0, 1) != '(' || substr($string, -1, 1) != ')') { - Return FALSE; + return FALSE; } $string = substr($string, 1, -1); @@ -5800,7 +5960,7 @@ public static function parseSearchBuilderString($string, $dataType = 'Integer') return FALSE; } - $returnValues = array(); + $returnValues = []; foreach ($values as $v) { if ($dataType == 'Integer' && !is_numeric($v)) { return FALSE; @@ -5831,7 +5991,7 @@ public function convertToPseudoNames(&$dao, $return = FALSE, $usedForAPI = FALSE if (empty($this->_pseudoConstantsSelect)) { return NULL; } - $values = array(); + $values = []; foreach ($this->_pseudoConstantsSelect as $key => $value) { if (!empty($this->_pseudoConstantsSelect[$key]['sorting'])) { continue; @@ -5891,7 +6051,7 @@ public function convertToPseudoNames(&$dao, $return = FALSE, $usedForAPI = FALSE $dao->$key = CRM_Core_PseudoConstant::stateProvinceAbbreviation($val); } // @todo handle this in the section above for pseudoconstants. - elseif (in_array($value['pseudoField'], array('participant_role_id', 'participant_role'))) { + elseif (in_array($value['pseudoField'], ['participant_role_id', 'participant_role'])) { // @todo define bao on this & merge into the above condition. $viewValues = explode(CRM_Core_DAO::VALUE_SEPARATOR, $val); @@ -5916,7 +6076,7 @@ public function convertToPseudoNames(&$dao, $return = FALSE, $usedForAPI = FALSE $lastElement = array_pop($keyVal); foreach ($keyVal as $v) { if (!array_key_exists($v, $current)) { - $current[$v] = array(); + $current[$v] = []; } $current = &$current[$v]; } @@ -5929,12 +6089,7 @@ public function convertToPseudoNames(&$dao, $return = FALSE, $usedForAPI = FALSE } } if (!$usedForAPI) { - foreach (array( - 'gender_id' => 'gender', - 'prefix_id' => 'individual_prefix', - 'suffix_id' => 'individual_suffix', - 'communication_style_id' => 'communication_style', - ) as $realField => $labelField) { + foreach ($this->legacyHackedFields as $realField => $labelField) { // This is a temporary routine for handling these fields while // we figure out how to handled them based on metadata in /// export and search builder. CRM-19815, CRM-19830. @@ -5957,7 +6112,7 @@ public function includePseudoFieldsJoin($sort) { return NULL; } $sort = is_string($sort) ? $sort : $sort->orderBy(); - $present = array(); + $present = []; foreach ($this->_pseudoConstantsSelect as $name => $value) { if (!empty($value['table'])) { @@ -5994,7 +6149,7 @@ public function includePseudoFieldsJoin($sort) { $this->_fromClause = $this->_fromClause . $presentClause; $this->_simpleFromClause = $this->_simpleFromClause . $presentSimpleFromClause; - return array($presentClause, $presentSimpleFromClause); + return [$presentClause, $presentSimpleFromClause]; } /** @@ -6017,7 +6172,7 @@ public static function buildQillForFieldValue( $fieldName, $fieldValue, $op, - $pseudoExtraParam = array(), + $pseudoExtraParam = [], $type = CRM_Utils_Type::T_STRING ) { $qillOperators = CRM_Core_SelectValues::getSearchBuilderOperators(); @@ -6031,7 +6186,7 @@ public static function buildQillForFieldValue( // if Operator chosen is NULL/EMPTY then if (strpos($op, 'NULL') !== FALSE || strpos($op, 'EMPTY') !== FALSE) { - return array(CRM_Utils_Array::value($op, $qillOperators, $op), ''); + return [CRM_Utils_Array::value($op, $qillOperators, $op), '']; } if ($fieldName == 'activity_type_id') { @@ -6047,7 +6202,8 @@ public static function buildQillForFieldValue( $pseudoOptions = CRM_Core_PseudoConstant::worldRegion(); } elseif ($daoName == 'CRM_Event_DAO_Event' && $fieldName == 'id') { - $pseudoOptions = CRM_Event_BAO_Event::getEvents(0, $fieldValue, TRUE, TRUE, TRUE); + $checkPermission = CRM_Utils_Array::value('check_permission', $pseudoExtraParam, TRUE); + $pseudoOptions = CRM_Event_BAO_Event::getEvents(0, $fieldValue, TRUE, $checkPermission, TRUE); } elseif ($fieldName == 'contribution_product_id') { $pseudoOptions = CRM_Contribute_PseudoConstant::products(); @@ -6063,7 +6219,7 @@ public static function buildQillForFieldValue( } if (is_array($fieldValue)) { - $qillString = array(); + $qillString = []; if (!empty($pseudoOptions)) { foreach ((array) $fieldValue as $val) { $qillString[] = CRM_Utils_Array::value($val, $pseudoOptions, $val); @@ -6093,7 +6249,7 @@ public static function buildQillForFieldValue( $fieldValue = CRM_Utils_Date::customFormat($fieldValue); } - return array(CRM_Utils_Array::value($op, $qillOperators, $op), $fieldValue); + return [CRM_Utils_Array::value($op, $qillOperators, $op), $fieldValue]; } /** @@ -6135,9 +6291,9 @@ public static function getWildCardedValue($wildcard, $op, $value) { * @param array $changeNames * Array of fields whose name should be changed */ - public static function processSpecialFormValue(&$formValues, $specialFields, $changeNames = array()) { + public static function processSpecialFormValue(&$formValues, $specialFields, $changeNames = []) { // Array of special fields whose value are considered only for NULL or EMPTY operators - $nullableFields = array('contribution_batch_id'); + $nullableFields = ['contribution_batch_id']; foreach ($specialFields as $element) { $value = CRM_Utils_Array::value($element, $formValues); @@ -6147,15 +6303,15 @@ public static function processSpecialFormValue(&$formValues, $specialFields, $ch unset($formValues[$element]); $element = $changeNames[$element]; } - $formValues[$element] = array('IN' => $value); + $formValues[$element] = ['IN' => $value]; } - elseif (in_array($value, array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'))) { - $formValues[$element] = array($value => 1); + elseif (in_array($value, ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY'])) { + $formValues[$element] = [$value => 1]; } elseif (!in_array($element, $nullableFields)) { // if wildcard is already present return searchString as it is OR append and/or prepend with wildcard $isWilcard = strstr($value, '%') ? FALSE : CRM_Core_Config::singleton()->includeWildCardInName; - $formValues[$element] = array('LIKE' => self::getWildCardedValue($isWilcard, 'LIKE', $value)); + $formValues[$element] = ['LIKE' => self::getWildCardedValue($isWilcard, 'LIKE', $value)]; } } } @@ -6172,20 +6328,17 @@ public static function processSpecialFormValue(&$formValues, $specialFields, $ch * * @param string|CRM_Utils_Sort $sort * The order by string. - * @param bool $sortByChar - * If true returns the distinct array of first characters for search results. * @param null $sortOrder * Who knows? Hu knows. He who knows Hu knows who. - * @param string $additionalFromClause - * Should be clause with proper joins, effective to reduce where clause load. + * * @return array * list(string $orderByClause, string $additionalFromClause). */ - protected function prepareOrderBy($sort, $sortByChar, $sortOrder, $additionalFromClause) { - $order = NULL; - $orderByArray = array(); - $config = CRM_Core_Config::singleton(); - if ($config->includeOrderByClause || + protected function prepareOrderBy($sort, $sortOrder) { + $orderByArray = []; + $orderBy = ''; + + if (CRM_Core_Config::singleton()->includeOrderByClause || isset($this->_distinctComponentClause) ) { if ($sort) { @@ -6206,106 +6359,104 @@ protected function prepareOrderBy($sort, $sortByChar, $sortOrder, $additionalFro $orderBy = str_replace('sort_name', 'contact_a.sort_name', $orderBy); } - $order = " ORDER BY $orderBy"; - if ($sortOrder) { - $order .= " $sortOrder"; + $orderBy .= " $sortOrder"; } // always add contact_a.id to the ORDER clause // so the order is deterministic - if (strpos('contact_a.id', $order) === FALSE) { - $order .= ", contact_a.id"; + if (strpos('contact_a.id', $orderBy) === FALSE) { + $orderBy .= ", contact_a.id"; } } } - elseif ($sortByChar) { - $orderByArray = array("UPPER(LEFT(contact_a.sort_name, 1)) asc"); - } else { - $order = " ORDER BY contact_a.sort_name asc, contact_a.id"; + $orderBy = " contact_a.sort_name ASC, contact_a.id"; } } - if (!$order && empty($orderByArray)) { - return array($order, $additionalFromClause); + if (!$orderBy) { + return NULL; } // Remove this here & add it at the end for simplicity. - $order = trim(str_replace('ORDER BY', '', $order)); + $order = trim($orderBy); + $orderByArray = explode(',', $order); - // hack for order clause - if (!empty($orderByArray)) { - $order = implode(', ', $orderByArray); - } - else { - $orderByArray = explode(',', $order); - } foreach ($orderByArray as $orderByClause) { $orderByClauseParts = explode(' ', trim($orderByClause)); $field = $orderByClauseParts[0]; $direction = isset($orderByClauseParts[1]) ? $orderByClauseParts[1] : 'asc'; + $fieldSpec = $this->getMetadataForRealField($field); - switch ($field) { - case 'city': - case 'postal_code': - $this->_tables["civicrm_address"] = $this->_whereTables["civicrm_address"] = 1; - $order = str_replace($field, "civicrm_address.{$field}", $order); - break; - - case 'country': - case 'state_province': - $this->_tables["civicrm_{$field}"] = $this->_whereTables["civicrm_{$field}"] = 1; - if (is_array($this->_returnProperties) && empty($this->_returnProperties)) { - $additionalFromClause .= " LEFT JOIN civicrm_{$field} ON civicrm_{$field}.id = civicrm_address.{$field}_id"; - } - $order = str_replace($field, "civicrm_{$field}.name", $order); - break; - - case 'email': - $this->_tables["civicrm_email"] = $this->_whereTables["civicrm_email"] = 1; - $order = str_replace($field, "civicrm_email.{$field}", $order); - break; - - default: - foreach ($this->_pseudoConstantsSelect as $key => $pseudoConstantMetadata) { - // By replacing the join to the option value table with the mysql construct - // ORDER BY field('contribution_status_id', 2,1,4) - // we can remove a join. In the case of the option value join it is - /// a join known to cause slow queries. - // @todo cover other pseudoconstant types. Limited to option group ones in the - // first instance for scope reasons. They require slightly different handling as the column (label) - // is not declared for them. - // @todo so far only integer fields are being handled. If we add string fields we need to look at - // escaping. - if (isset($pseudoConstantMetadata['pseudoconstant']) - && isset($pseudoConstantMetadata['pseudoconstant']['optionGroupName']) - && $field === CRM_Utils_Array::value('optionGroupName', $pseudoConstantMetadata['pseudoconstant']) - ) { - $sortedOptions = $pseudoConstantMetadata['bao']::buildOptions($pseudoConstantMetadata['pseudoField'], NULL, array( - 'orderColumn' => 'label', - )); - $order = str_replace("$field $direction", "field({$pseudoConstantMetadata['pseudoField']}," . implode(',', array_keys($sortedOptions)) . ") $direction", $order); - } - //CRM-12565 add "`" around $field if it is a pseudo constant - // This appears to be for 'special' fields like locations with appended numbers or hyphens .. maybe. - if (!empty($pseudoConstantMetadata['element']) && $pseudoConstantMetadata['element'] == $field) { - $order = str_replace($field, "`{$field}`", $order); - } - } + // This is a hacky add-in for primary address joins. Feel free to iterate as it is unit tested. + // @todo much more cleanup on location handling in addHierarchical elements. Potentially + // add keys to $this->fields to represent the actual keys for locations. + if (empty($fieldSpec) && substr($field, 0, 2) === '1-') { + $fieldSpec = $this->getMetadataForField(substr($field, 2)); + $this->addAddressTable('1-' . str_replace('civicrm_', '', $fieldSpec['table_name']), 'is_primary = 1'); } - } + + if ($this->_returnProperties === []) { + if (!empty($fieldSpec['table_name']) && !isset($this->_tables[$fieldSpec['table_name']])) { + $this->_tables[$fieldSpec['table_name']] = 1; + $order = $fieldSpec['where'] . ' ' . $direction; + } + + } + $cfID = CRM_Core_BAO_CustomField::getKeyID($field); + // add to cfIDs array if not present + if (!empty($cfID) && !array_key_exists($cfID, $this->_cfIDs)) { + $this->_cfIDs[$cfID] = []; + $this->_customQuery = new CRM_Core_BAO_CustomQuery($this->_cfIDs, TRUE, $this->_locationSpecificCustomFields); + $this->_customQuery->query(); + $this->_select = array_merge($this->_select, $this->_customQuery->_select); + $this->_tables = array_merge($this->_tables, $this->_customQuery->_tables); + } + + // By replacing the join to the option value table with the mysql construct + // ORDER BY field('contribution_status_id', 2,1,4) + // we can remove a join. In the case of the option value join it is + /// a join known to cause slow queries. + // @todo cover other pseudoconstant types. Limited to option group ones & Foreign keys + // matching an id+name parrern in the + // first instance for scope reasons. They require slightly different handling as the column (label) + // is not declared for them. + // @todo so far only integer fields are being handled. If we add string fields we need to look at + // escaping. + $pseudoConstantMetadata = CRM_Utils_Array::value('pseudoconstant', $fieldSpec, FALSE); + if (!empty($pseudoConstantMetadata) + ) { + if (!empty($pseudoConstantMetadata['optionGroupName']) + || $this->isPseudoFieldAnFK($fieldSpec) + ) { + // dev/core#1305 @todo this is not the right thing to do but for now avoid fatal error + if (empty($fieldSpec['bao'])) { + continue; + } + $sortedOptions = $fieldSpec['bao']::buildOptions($fieldSpec['name'], NULL, [ + 'orderColumn' => CRM_Utils_Array::value('labelColumn', $pseudoConstantMetadata, 'label'), + ]); + $fieldIDsInOrder = implode(',', array_keys($sortedOptions)); + // Pretty sure this validation ALSO happens in the order clause & this can't be reached but... + // this might give some early warning. + CRM_Utils_Type::validate($fieldIDsInOrder, 'CommaSeparatedIntegers'); + $order = str_replace("$field", "field({$fieldSpec['name']},$fieldIDsInOrder)", $order); + } + //CRM-12565 add "`" around $field if it is a pseudo constant + // This appears to be for 'special' fields like locations with appended numbers or hyphens .. maybe. + if (!empty($pseudoConstantMetadata['element']) && $pseudoConstantMetadata['element'] == $field) { + $order = str_replace($field, "`{$field}`", $order); + } + } + } $this->_fromClause = self::fromClause($this->_tables, NULL, NULL, $this->_primaryLocation, $this->_mode); $this->_simpleFromClause = self::fromClause($this->_whereTables, NULL, NULL, $this->_primaryLocation, $this->_mode); // The above code relies on crazy brittle string manipulation of a peculiarly-encoded ORDER BY // clause. But this magic helper which forgivingly reescapes ORDER BY. - // Note: $sortByChar implies that $order was hard-coded/trusted, so it can do funky things. - if ($sortByChar) { - return array(' ORDER BY ' . $order, $additionalFromClause); - } if ($order) { $order = CRM_Utils_Type::escape($order, 'MysqlOrderBy'); - return array(' ORDER BY ' . $order, $additionalFromClause); + return ' ORDER BY ' . $order; } } @@ -6343,11 +6494,11 @@ public function convertGroupIDStringToLabelString(&$dao, $val) { public function setQillAndWhere($name, $op, $value, $grouping, $field) { $this->_where[$grouping][] = self::buildClause("contact_a.{$name}", $op, $value); list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $name, $value, $op); - $this->_qill[$grouping][] = ts("%1 %2 %3", array( + $this->_qill[$grouping][] = ts("%1 %2 %3", [ 1 => $field['title'], 2 => $qillop, 3 => $qillVal, - )); + ]); } /** @@ -6358,21 +6509,651 @@ public function setQillAndWhere($name, $op, $value, $grouping, $field) { * have been requested. Payment_instrument is the option groun name field value. * * @param array $field + * @param string $fieldName + * The unique name of the field - ie. the one it will be aliased to in the query. * * @return bool */ - private function pseudoConstantNameIsInReturnProperties($field) { - if (!isset($field['pseudoconstant']['optionGroupName'])) { + private function pseudoConstantNameIsInReturnProperties($field, $fieldName = NULL) { + $realField = $this->getMetadataForRealField($fieldName); + if (!isset($realField['pseudoconstant'])) { + return FALSE; + } + $pseudoConstant = $realField['pseudoconstant']; + if (empty($pseudoConstant['optionGroupName']) && + CRM_Utils_Array::value('labelColumn', $pseudoConstant) !== 'name') { + // We are increasing our pseudoconstant handling - but still very cautiously, + // hence the check for labelColumn === name return FALSE; } - if (CRM_Utils_Array::value($field['pseudoconstant']['optionGroupName'], $this->_returnProperties)) { + if (!empty($pseudoConstant['optionGroupName']) && CRM_Utils_Array::value($pseudoConstant['optionGroupName'], $this->_returnProperties)) { + return TRUE; + } + if (CRM_Utils_Array::value($fieldName, $this->_returnProperties)) { return TRUE; } + // Is this still required - the above goes off the unique name. Test with things like + // communication_preferences & prefix_id. if (CRM_Utils_Array::value($field['name'], $this->_returnProperties)) { return TRUE; } return FALSE; } + /** + * Get Select Clause. + * + * @return string + */ + public function getSelect() { + $select = "SELECT "; + if (isset($this->_distinctComponentClause)) { + $select .= "{$this->_distinctComponentClause}, "; + } + $select .= implode(', ', $this->_select); + return $select; + } + + /** + * Add basic statistics to the summary. + * + * @param array $summary + * @param string $where + * @param string $from + * + * + * @return array + * @throws \CRM_Core_Exception + */ + protected function addBasicStatsToSummary(&$summary, $where, $from) { + $summary['total']['count'] = 0; + $summary['total']['amount'] = $summary['total']['avg'] = []; + + $query = " + SELECT COUNT( conts.total_amount ) as total_count, + SUM( conts.total_amount ) as total_amount, + AVG( conts.total_amount ) as total_avg, + conts.currency as currency + FROM ( + SELECT civicrm_contribution.total_amount, COUNT(civicrm_contribution.total_amount) as civicrm_contribution_total_amount_count, + civicrm_contribution.currency + $from + $where AND civicrm_contribution.contribution_status_id = 1 + GROUP BY civicrm_contribution.id + ) as conts + GROUP BY currency"; + + $dao = CRM_Core_DAO::executeQuery($query); + + while ($dao->fetch()) { + $summary['total']['count'] += $dao->total_count; + $summary['total']['amount'][] = CRM_Utils_Money::format($dao->total_amount, $dao->currency); + $summary['total']['avg'][] = CRM_Utils_Money::format($dao->total_avg, $dao->currency); + } + + if (!empty($summary['total']['amount'])) { + $summary['total']['amount'] = implode(', ', $summary['total']['amount']); + $summary['total']['avg'] = implode(', ', $summary['total']['avg']); + } + else { + $summary['total']['amount'] = $summary['total']['avg'] = 0; + } + return $summary; + } + + /** + * Add basic soft credit statistics to summary array. + * + * @param array $summary + * @param string $where + * @param string $from + */ + protected function addBasicSoftCreditStatsToStats(&$summary, $where, $from) { + $query = " + SELECT COUNT( conts.total_amount ) as total_count, + SUM( conts.total_amount ) as total_amount, + AVG( conts.total_amount ) as total_avg, + conts.currency as currency + FROM ( + SELECT civicrm_contribution_soft.amount as total_amount, civicrm_contribution_soft.currency + $from + $where AND civicrm_contribution.contribution_status_id = 1 AND civicrm_contribution_soft.id IS NOT NULL + GROUP BY civicrm_contribution_soft.id + ) as conts + GROUP BY currency"; + + $dao = CRM_Core_DAO::executeQuery($query); + $summary['soft_credit']['count'] = 0; + $summary['soft_credit']['amount'] = $summary['soft_credit']['avg'] = []; + while ($dao->fetch()) { + $summary['soft_credit']['count'] += $dao->total_count; + $summary['soft_credit']['amount'][] = CRM_Utils_Money::format($dao->total_amount, $dao->currency); + $summary['soft_credit']['avg'][] = CRM_Utils_Money::format($dao->total_avg, $dao->currency); + } + if (!empty($summary['soft_credit']['amount'])) { + $summary['soft_credit']['amount'] = implode(', ', $summary['soft_credit']['amount']); + $summary['soft_credit']['avg'] = implode(', ', $summary['soft_credit']['avg']); + } + else { + $summary['soft_credit']['amount'] = $summary['soft_credit']['avg'] = 0; + } + } + + /** + * Add basic stats about cancelled contributions to the summary. + * + * @param array $summary + * @param string $where + * @param string $from + */ + protected function addBasicCancelStatsToSummary(&$summary, $where, $from) { + $query = " + SELECT COUNT( conts.total_amount ) as cancel_count, + SUM( conts.total_amount ) as cancel_amount, + AVG( conts.total_amount ) as cancel_avg, + conts.currency as currency + FROM ( + SELECT civicrm_contribution.total_amount, civicrm_contribution.currency + $from + $where AND civicrm_contribution.cancel_date IS NOT NULL + GROUP BY civicrm_contribution.id + ) as conts + GROUP BY currency"; + + $dao = CRM_Core_DAO::executeQuery($query); + + if ($dao->N <= 1) { + if ($dao->fetch()) { + $summary['cancel']['count'] = $dao->cancel_count; + $summary['cancel']['amount'] = CRM_Utils_Money::format($dao->cancel_amount, $dao->currency); + $summary['cancel']['avg'] = CRM_Utils_Money::format($dao->cancel_avg, $dao->currency); + } + } + else { + $summary['cancel']['count'] = 0; + $summary['cancel']['amount'] = $summary['cancel']['avg'] = []; + while ($dao->fetch()) { + $summary['cancel']['count'] += $dao->cancel_count; + $summary['cancel']['amount'][] = CRM_Utils_Money::format($dao->cancel_amount, $dao->currency); + $summary['cancel']['avg'][] = CRM_Utils_Money::format($dao->cancel_avg, $dao->currency); + } + $summary['cancel']['amount'] = implode(', ', $summary['cancel']['amount']); + $summary['cancel']['avg'] = implode(', ', $summary['cancel']['avg']); + } + } + + /** + * Create the sql query for an contact search. + * + * @param int $offset + * The offset for the query. + * @param int $rowCount + * The number of rows to return. + * @param string|CRM_Utils_Sort $sort + * The order by string. + * @param bool $count + * Is this a count only query ?. + * @param bool $includeContactIds + * Should we include contact ids?. + * @param bool $sortByChar + * If true returns the distinct array of first characters for search results. + * @param bool $groupContacts + * If true, return only the contact ids. + * @param string $additionalWhereClause + * If the caller wants to further restrict the search (used for components). + * @param null $sortOrder + * @param string $additionalFromClause + * Should be clause with proper joins, effective to reduce where clause load. + * + * @param bool $skipOrderAndLimit + * @return string + */ + public function getSearchSQL( + $offset = 0, $rowCount = 0, $sort = NULL, + $count = FALSE, $includeContactIds = FALSE, + $sortByChar = FALSE, $groupContacts = FALSE, + $additionalWhereClause = NULL, $sortOrder = NULL, + $additionalFromClause = NULL, $skipOrderAndLimit = FALSE) { + + $sqlParts = $this->getSearchSQLParts($offset, $rowCount, $sort, $count, $includeContactIds, $sortByChar, $groupContacts, $additionalWhereClause, $sortOrder, $additionalFromClause); + + if ($sortByChar) { + CRM_Core_Error::deprecatedFunctionWarning('sort by char is deprecated - use alphabetQuery method'); + $sqlParts['order_by'] = 'ORDER BY sort_name asc'; + } + + if ($skipOrderAndLimit) { + CRM_Core_Error::deprecatedFunctionWarning('skipOrderAndLimit is deprected - call getSearchSQLParts & construct it in the calling function'); + $query = "{$sqlParts['select']} {$sqlParts['from']} {$sqlParts['where']} {$sqlParts['having']} {$sqlParts['group_by']}"; + } + else { + $query = "{$sqlParts['select']} {$sqlParts['from']} {$sqlParts['where']} {$sqlParts['having']} {$sqlParts['group_by']} {$sqlParts['order_by']} {$sqlParts['limit']}"; + } + return $query; + } + + /** + * Get the component parts of the search query as an array. + * + * @param int $offset + * The offset for the query. + * @param int $rowCount + * The number of rows to return. + * @param string|CRM_Utils_Sort $sort + * The order by string. + * @param bool $count + * Is this a count only query ?. + * @param bool $includeContactIds + * Should we include contact ids?. + * @param bool $sortByChar + * If true returns the distinct array of first characters for search results. + * @param bool $groupContacts + * If true, return only the contact ids. + * @param string $additionalWhereClause + * If the caller wants to further restrict the search (used for components). + * @param null $sortOrder + * @param string $additionalFromClause + * Should be clause with proper joins, effective to reduce where clause load. + * + * @return array + */ + public function getSearchSQLParts($offset = 0, $rowCount = 0, $sort = NULL, + $count = FALSE, $includeContactIds = FALSE, + $sortByChar = FALSE, $groupContacts = FALSE, + $additionalWhereClause = NULL, $sortOrder = NULL, + $additionalFromClause = NULL) { + if ($includeContactIds) { + $this->_includeContactIds = TRUE; + $this->_whereClause = $this->whereClause(); + } + $onlyDeleted = in_array([ + 'deleted_contacts', + '=', + '1', + '0', + '0', + ], $this->_params); + + // if we’re explicitly looking for a certain contact’s contribs, events, etc. + // and that contact happens to be deleted, set $onlyDeleted to true + foreach ($this->_params as $values) { + $name = CRM_Utils_Array::value(0, $values); + $op = CRM_Utils_Array::value(1, $values); + $value = CRM_Utils_Array::value(2, $values); + if ($name == 'contact_id' and $op == '=') { + if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'is_deleted')) { + $onlyDeleted = TRUE; + } + break; + } + } + + // building the query string + $groupBy = $groupByCols = NULL; + if (!$count) { + if (isset($this->_groupByComponentClause)) { + $groupByCols = preg_replace('/^GROUP BY /', '', trim($this->_groupByComponentClause)); + $groupByCols = explode(', ', $groupByCols); + } + elseif ($this->_useGroupBy) { + $groupByCols = ['contact_a.id']; + } + } + if ($this->_mode & CRM_Contact_BAO_Query::MODE_ACTIVITY && (!$count)) { + $groupByCols = ['civicrm_activity.id']; + } + if (!empty($groupByCols)) { + $groupBy = " GROUP BY " . implode(', ', $groupByCols); + } + + $order = $orderBy = ''; + if (!$count) { + if (!$sortByChar) { + $order = $this->prepareOrderBy($sort, $sortOrder); + } + } + // Cases where we are disabling FGB (FULL_GROUP_BY_MODE): + // 1. When GROUP BY columns are present then disable FGB otherwise it demands to add ORDER BY columns in GROUP BY and eventually in SELECT + // clause. This will impact the search query output. + $disableFullGroupByMode = (!empty($groupBy) || $groupContacts); + + if ($disableFullGroupByMode) { + CRM_Core_DAO::disableFullGroupByMode(); + } + + // CRM-15231 + $this->_sort = $sort; + + //CRM-15967 + $this->includePseudoFieldsJoin($sort); + + list($select, $from, $where, $having) = $this->query($count, $sortByChar, $groupContacts, $onlyDeleted); + + if ($additionalWhereClause) { + $where = $where . ' AND ' . $additionalWhereClause; + } + + //additional from clause should be w/ proper joins. + if ($additionalFromClause) { + $from .= "\n" . $additionalFromClause; + } + + // if we are doing a transform, do it here + // use the $from, $where and $having to get the contact ID + if ($this->_displayRelationshipType) { + $this->filterRelatedContacts($from, $where, $having); + } + $limit = (!$count && $rowCount) ? " LIMIT " . CRM_Utils_Type::escape($offset, 'Int') . ", " . CRM_Utils_Type::escape($rowCount, 'Int') : ''; + + return [ + 'select' => $select, + 'from' => $from, + 'where' => $where, + 'order_by' => $order, + 'group_by' => $groupBy, + 'having' => $having, + 'limit' => $limit, + ]; + } + + /** + * Get the metadata for a given field. + * + * @param string $fieldName + * + * @return array + */ + protected function getMetadataForField($fieldName) { + if ($fieldName === 'contact_a.id') { + // This seems to be the only anomaly. + $fieldName = 'id'; + } + $pseudoField = isset($this->_pseudoConstantsSelect[$fieldName]) ? $this->_pseudoConstantsSelect[$fieldName] : []; + $field = isset($this->_fields[$fieldName]) ? $this->_fields[$fieldName] : $pseudoField; + $field = array_merge($field, $pseudoField); + if (!empty($field) && empty($field['name'])) { + // standardising field formatting here - over time we can phase out variants. + // all paths using this currently unit tested + $field['name'] = CRM_Utils_Array::value('field_name', $field, CRM_Utils_Array::value('idCol', $field, $fieldName)); + } + return $field; + } + + /** + * Get the metadata for a given field, returning the 'real field' if it is a pseudofield. + * + * @param string $fieldName + * + * @return array + */ + protected function getMetadataForRealField($fieldName) { + $field = $this->getMetadataForField($fieldName); + if (!empty($field['is_pseudofield_for'])) { + $field = $this->getMetadataForField($field['is_pseudofield_for']); + $field['pseudofield_name'] = $fieldName; + } + elseif (!empty($field['pseudoconstant'])) { + if (!empty($field['pseudoconstant']['optionGroupName'])) { + $field['pseudofield_name'] = $field['pseudoconstant']['optionGroupName']; + if (empty($field['table_name'])) { + if (!empty($field['where'])) { + $field['table_name'] = explode('.', $field['where'])[0]; + } + else { + $field['table_name'] = 'civicrm_contact'; + } + } + } + } + return $field; + } + + /** + * If we have a field that is better rendered via the pseudoconstant handled them here. + * + * Rather than joining in the additional table we render the option value on output. + * + * @todo - so far this applies to a narrow range of pseudocontants. We are adding them + * carefully with test coverage but aim to extend. + * + * @param string $name + */ + protected function addPseudoconstantFieldToSelect($name) { + $field = $this->getMetadataForRealField($name); + $realFieldName = $field['name']; + $pseudoFieldName = CRM_Utils_Array::value('pseudofield_name', $field); + if ($pseudoFieldName) { + // @todo - we don't really need to build this array now we have metadata more available with getMetadataForField fn. + $this->_pseudoConstantsSelect[$pseudoFieldName] = [ + 'pseudoField' => $pseudoFieldName, + 'idCol' => $realFieldName, + 'field_name' => $field['name'], + 'bao' => $field['bao'], + 'pseudoconstant' => $field['pseudoconstant'], + ]; + } + + $this->_tables[$field['table_name']] = 1; + $this->_element[$realFieldName] = 1; + $this->_select[$field['name']] = str_replace('civicrm_contact.', 'contact_a.', "{$field['where']} as `$realFieldName`"); + } + + /** + * Is this pseudofield a foreign key constraint. + * + * We are trying to cautiously expand our pseudoconstant handling. This check allows us + * to extend to a narrowly defined type (and then only if the pseudofield is in the fields + * array which is done for contributions which are mostly handled as pseudoconstants. + * + * @param $fieldSpec + * + * @return bool + */ + protected function isPseudoFieldAnFK($fieldSpec) { + if (empty($fieldSpec['FKClassName']) + || CRM_Utils_Array::value('keyColumn', $fieldSpec['pseudoconstant']) !== 'id' + || CRM_Utils_Array::value('labelColumn', $fieldSpec['pseudoconstant']) !== 'name') { + return FALSE; + } + return TRUE; + } + + /** + * Is the field a relative date field. + * + * @param string $fieldName + * + * @return bool + */ + protected function isARelativeDateField($fieldName) { + if (substr($fieldName, -9, 9) !== '_relative') { + return FALSE; + } + $realField = substr($fieldName, 0, strlen($fieldName) - 9); + return isset($this->_fields[$realField]); + } + + /** + * Get the specifications for the field, if available. + * + * @param string $fieldName + * Fieldname as displayed on the form. + * + * @return array + */ + public function getFieldSpec($fieldName) { + if (isset($this->_fields[$fieldName])) { + return $this->_fields[$fieldName]; + } + $lowFieldName = str_replace('_low', '', $fieldName); + if (isset($this->_fields[$lowFieldName])) { + return array_merge($this->_fields[$lowFieldName], ['field_name' => $lowFieldName]); + } + $highFieldName = str_replace('_high', '', $fieldName); + if (isset($this->_fields[$highFieldName])) { + return array_merge($this->_fields[$highFieldName], ['field_name' => $highFieldName]); + } + return []; + } + + public function buildWhereForDate() { + + } + + /** + * Is the field a relative date field. + * + * @param string $fieldName + * + * @return bool + */ + protected function isADateRangeField($fieldName) { + if (substr($fieldName, -4, 4) !== '_low' && substr($fieldName, -5, 5) !== '_high') { + return FALSE; + } + return !empty($this->getFieldSpec($fieldName)); + } + + /** + * @param $values + */ + protected function buildRelativeDateQuery(&$values) { + $value = CRM_Utils_Array::value(2, $values); + if (empty($value)) { + return; + } + $fieldName = substr($values[0], 0, strlen($values[0]) - 9); + $fieldSpec = $this->_fields[$fieldName]; + $tableName = $fieldSpec['table_name']; + $filters = CRM_Core_OptionGroup::values('relative_date_filters'); + $grouping = CRM_Utils_Array::value(3, $values); + // If the table value is already set for a custom field it will be more nuanced than just '1'. + $this->_tables[$tableName] = $this->_tables[$tableName] ?? 1; + $this->_whereTables[$tableName] = $this->_whereTables[$tableName] ?? 1; + + $dates = CRM_Utils_Date::getFromTo($value, NULL, NULL); + if (empty($dates[0])) { + // ie. no start date we only have end date + $this->_where[$grouping][] = $fieldSpec['where'] . " <= '{$dates[1]}'"; + + $this->_qill[$grouping][] = ts('%1 is ', [$fieldSpec['title']]) . $filters[$value] . ' (' . ts("to %1", [ + CRM_Utils_Date::customFormat($dates[1]), + ]) . ')'; + } + elseif (empty($dates[1])) { + // ie. no end date we only have start date + $this->_where[$grouping][] = $fieldSpec['where'] . " >= '{$dates[1]}'"; + + $this->_qill[$grouping][] = ts('%1 is ', [$fieldSpec['title']]) . $filters[$value] . ' (' . ts("from %1", [ + CRM_Utils_Date::customFormat($dates[0]), + ]) . ')'; + } + else { + // we have start and end dates. + $this->_where[$grouping][] = $fieldSpec['where'] . " BETWEEN '{$dates[0]}' AND '{$dates[1]}'"; + + $this->_qill[$grouping][] = ts('%1 is ', [$fieldSpec['title']]) . $filters[$value] . ' (' . ts("between %1 and %2", [ + CRM_Utils_Date::customFormat($dates[0]), + CRM_Utils_Date::customFormat($dates[1]), + ]) . ')'; + } + } + + /** + * Build the query for a date field if it is a _high or _low field. + * + * @param $values + * + * @return bool + */ + public function buildDateRangeQuery($values) { + if ($this->isADateRangeField($values[0])) { + $fieldSpec = $this->getFieldSpec($values[0]); + $title = empty($fieldSpec['unique_title']) ? $fieldSpec['title'] : $fieldSpec['unique_title']; + $this->dateQueryBuilder($values, $fieldSpec['table_name'], $fieldSpec['field_name'], $fieldSpec['name'], $title); + return TRUE; + } + return FALSE; + } + + /** + * Add the address table into the query. + * + * @param string $tableKey + * @param string $joinCondition + * + * @return array + * - alias name + * - address join. + */ + protected function addAddressTable($tableKey, $joinCondition) { + $tName = "$tableKey-address"; + $aName = "`$tableKey-address`"; + $this->_select["{$tName}_id"] = "`$tName`.id as `{$tName}_id`"; + $this->_element["{$tName}_id"] = 1; + $addressJoin = "\nLEFT JOIN civicrm_address $aName ON ($aName.contact_id = contact_a.id AND $aName.$joinCondition)"; + $this->_tables[$tName] = $addressJoin; + + return [ + $aName, + $addressJoin, + ]; + } + + /** + * Get the clause for group status. + * + * @param int $grouping + * + * @return string + */ + protected function getGroupStatusClause($grouping) { + $statuses = $this->getSelectedGroupStatuses($grouping); + return "status IN (" . implode(', ', $statuses) . ")"; + } + + /** + * Get an array of the statuses that have been selected. + * + * @param string $grouping + * + * @return array + */ + protected function getSelectedGroupStatuses($grouping) { + $statuses = []; + $gcsValues = $this->getWhereValues('group_contact_status', $grouping); + if ($gcsValues && + is_array($gcsValues[2]) + ) { + foreach ($gcsValues[2] as $k => $v) { + if ($v) { + $statuses[] = "'" . CRM_Utils_Type::escape($k, 'String') . "'"; + } + } + } + else { + $statuses[] = "'Added'"; + } + return $statuses; + } + + /** + * Get the qill value for the field. + * + * @param $name + * @param array|int|string $value + * @param $op + * + * @return string + */ + protected function getQillForField($name, $value, $op): string { + list($qillop, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $name, $value, $op); + return (string) ts("%1 %2 %3", [ + 1 => ts('Email'), + 2 => $qillop, + 3 => $qillVal, + ]); + } + } diff --git a/CRM/Contact/BAO/Query/Hook.php b/CRM/Contact/BAO/Query/Hook.php index 1b337fd3a07c..d4ef2a38b465 100644 --- a/CRM/Contact/BAO/Query/Hook.php +++ b/CRM/Contact/BAO/Query/Hook.php @@ -1,9 +1,9 @@ _queryObjects === NULL) { - $this->_queryObjects = array(); + $this->_queryObjects = []; CRM_Utils_Hook::queryObjects($this->_queryObjects, 'Contact'); } return $this->_queryObjects; @@ -72,7 +74,7 @@ public function getSearchQueryObjects() { * @return array */ public function &getFields() { - $extFields = array(); + $extFields = []; foreach (self::getSearchQueryObjects() as $obj) { $flds = $obj->getFields(); $extFields = array_merge($extFields, $flds); diff --git a/CRM/Contact/BAO/Query/Interface.php b/CRM/Contact/BAO/Query/Interface.php index 0987aa61b8f3..17e3202d7a98 100644 --- a/CRM/Contact/BAO/Query/Interface.php +++ b/CRM/Contact/BAO/Query/Interface.php @@ -1,9 +1,9 @@ $relationship->contact_id_b, 'contact' => $params['contact_id_a'], - ); + ]; //CRM-16087 removed additional call to function relatedMemberships which is already called by disableEnableRelationship //resulting in membership being created twice @@ -111,7 +123,7 @@ public static function createMultiple($params, $primaryContactLetter) { $secondaryContactLetter = ($primaryContactLetter == 'a') ? 'b' : 'a'; $secondaryContactIDs = $params['contact_id_' . $secondaryContactLetter]; $valid = $invalid = $duplicate = $saved = 0; - $relationshipIDs = array(); + $relationshipIDs = []; foreach ($secondaryContactIDs as $secondaryContactID) { try { $params['contact_id_' . $secondaryContactLetter] = $secondaryContactID; @@ -135,17 +147,18 @@ public static function createMultiple($params, $primaryContactLetter) { } } - return array( + return [ 'valid' => $valid, 'invalid' => $invalid, 'duplicate' => $duplicate, 'saved' => $saved, 'relationship_ids' => $relationshipIDs, - ); + ]; } /** * Takes an associative array and creates a relationship object. + * * @deprecated For single creates use the api instead (it's tested). * For multiple a new variant of this function needs to be written and migrated to as this is a bit * nasty @@ -157,11 +170,12 @@ public static function createMultiple($params, $primaryContactLetter) { * per http://wiki.civicrm.org/confluence/display/CRM/Database+layer * "we are moving away from the $ids param " * - * @return CRM_Contact_BAO_Relationship + * @return array + * @throws \CRM_Core_Exception */ - public static function legacyCreateMultiple(&$params, $ids = array()) { + public static function legacyCreateMultiple(&$params, $ids = []) { $valid = $invalid = $duplicate = $saved = 0; - $relationships = $relationshipIds = array(); + $relationships = $relationshipIds = []; $relationshipId = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params)); //CRM-9015 - the hooks are called here & in add (since add doesn't call create) @@ -173,15 +187,16 @@ public static function legacyCreateMultiple(&$params, $ids = array()) { $hook = 'edit'; } + // @todo pre hook is called from add - remove it from here CRM_Utils_Hook::pre($hook, 'Relationship', $relationshipId, $params); if (!$relationshipId) { // creating a new relationship $dataExists = self::dataExists($params); if (!$dataExists) { - return array(FALSE, TRUE, FALSE, FALSE, NULL); + return [FALSE, TRUE, FALSE, FALSE, NULL]; } - $relationshipIds = array(); + $relationshipIds = []; foreach ($params['contact_check'] as $key => $value) { // check if the relationship is valid between contacts. // step 1: check if the relationship is valid if not valid skip and keep the count @@ -236,7 +251,7 @@ public static function legacyCreateMultiple(&$params, $ids = array()) { ) ) { $duplicate++; - return array($valid, $invalid, $duplicate, $saved, NULL); + return [$valid, $invalid, $duplicate, $saved, NULL]; } $validContacts = TRUE; @@ -263,54 +278,81 @@ public static function legacyCreateMultiple(&$params, $ids = array()) { self::addRecent($params, $relationship); } - return array($valid, $invalid, $duplicate, $saved, $relationshipIds, $relationships); + return [$valid, $invalid, $duplicate, $saved, $relationshipIds, $relationships]; } /** * This is the function that check/add if the relationship created is valid. * * @param array $params - * (reference ) an assoc array of name/value pairs. + * Array of name/value pairs. * @param array $ids * The array that holds all the db ids. * @param int $contactId * This is contact id for adding relationship. * * @return CRM_Contact_BAO_Relationship + * + * @throws \CiviCRM_API3_Exception */ - public static function add(&$params, $ids = array(), $contactId = NULL) { - $relationshipId = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params)); + public static function add($params, $ids = [], $contactId = NULL) { + $params['id'] = CRM_Utils_Array::value('relationship', $ids, CRM_Utils_Array::value('id', $params)); $hook = 'create'; - if ($relationshipId) { + if ($params['id']) { $hook = 'edit'; } - //@todo hook are called from create and add - remove one - CRM_Utils_Hook::pre($hook, 'Relationship', $relationshipId, $params); + CRM_Utils_Hook::pre($hook, 'Relationship', $params['id'], $params); $relationshipTypes = CRM_Utils_Array::value('relationship_type_id', $params); - // explode the string with _ to get the relationship type id // and to know which contact has to be inserted in // contact_id_a and which one in contact_id_b - list($type) = explode('_', $relationshipTypes); + list($relationshipTypeID) = explode('_', $relationshipTypes); + + $relationship = new CRM_Contact_BAO_Relationship(); + if (!empty($params['id'])) { + $relationship->id = $params['id']; + // Only load the relationship if we're missing required params + $requiredParams = ['contact_id_a', 'contact_id_b', 'relationship_type_id']; + foreach ($requiredParams as $requiredKey) { + if (!isset($params[$requiredKey])) { + $relationship->find(TRUE); + break; + } + } + + } + $relationship->copyValues($params); + // @todo we could probably set $params['relationship_type_id'] above but it's unclear + // what that would do with the code below this. So for now be conservative and set it manually. + if (!empty($relationshipTypeID)) { + $relationship->relationship_type_id = $relationshipTypeID; + } + + $params['contact_id_a'] = $relationship->contact_id_a; + $params['contact_id_b'] = $relationship->contact_id_b; // check if the relationship type is Head of Household then update the // household's primary contact with this contact. - if ($type == 6) { - CRM_Contact_BAO_Household::updatePrimaryContact($params['contact_id_b'], $params['contact_id_a']); + try { + $headOfHouseHoldID = civicrm_api3('RelationshipType', 'getvalue', [ + 'return' => "id", + 'name_a_b' => "Head of Household for", + ]); + if ($relationshipTypeID == $headOfHouseHoldID) { + CRM_Contact_BAO_Household::updatePrimaryContact($relationship->contact_id_b, $relationship->contact_id_a); + } + } + catch (Exception $e) { + // No "Head of Household" relationship found so we skip specific processing } - $relationship = new CRM_Contact_BAO_Relationship(); - //@todo this code needs to be updated for the possibility that not all fields are set - // by using $relationship->copyValues($params); - // (update) - $relationship->contact_id_b = $params['contact_id_b']; - $relationship->contact_id_a = $params['contact_id_a']; - $relationship->relationship_type_id = $type; - $relationship->id = $relationshipId; + if (!empty($params['id']) && self::isCurrentEmployerNeedingToBeCleared($relationship->toArray(), $params['id'], $relationshipTypeID)) { + CRM_Contact_BAO_Contact_Utils::clearCurrentEmployer($relationship->contact_id_a); + } - $dateFields = array('end_date', 'start_date'); + $dateFields = ['end_date', 'start_date']; foreach (self::getdefaults() as $defaultField => $defaultValue) { if (isset($params[$defaultField])) { @@ -324,20 +366,29 @@ public static function add(&$params, $ids = array(), $contactId = NULL) { $relationship->$defaultField = $params[$defaultField]; } } - elseif (!$relationshipId) { + elseif (empty($params['id'])) { $relationship->$defaultField = $defaultValue; } } $relationship->save(); - + // is_current_employer is an optional parameter that triggers updating the employer_id field to reflect + // the relationship being updated. As of writing only truthy versions of the parameter are respected. + // https://github.com/civicrm/civicrm-core/pull/13331 attempted to cover both but stalled in QA + // so currently we have a cut down version. + if (!empty($params['is_current_employer'])) { + if (!$relationship->relationship_type_id || !$relationship->contact_id_a || !$relationship->contact_id_b) { + $relationship->fetch(); + } + if (self::isRelationshipTypeCurrentEmployer($relationship->relationship_type_id)) { + CRM_Contact_BAO_Contact_Utils::setCurrentEmployer([$relationship->contact_id_a => $relationship->contact_id_b]); + } + } // add custom field values if (!empty($params['custom'])) { CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_relationship', $relationship->id); } - $relationship->free(); - CRM_Utils_Hook::post($hook, 'Relationship', $relationship->id, $relationship); return $relationship; @@ -354,19 +405,19 @@ public static function addRecent($params, $relationship) { "action=view&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&context=home" ); $session = CRM_Core_Session::singleton(); - $recentOther = array(); + $recentOther = []; if (($session->get('userID') == $relationship->contact_id_a) || CRM_Contact_BAO_Contact_Permission::allow($relationship->contact_id_a, CRM_Core_Permission::EDIT) ) { $rType = substr(CRM_Utils_Array::value('relationship_type_id', $params), -3); - $recentOther = array( + $recentOther = [ 'editUrl' => CRM_Utils_System::url('civicrm/contact/view/rel', "action=update&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home" ), 'deleteUrl' => CRM_Utils_System::url('civicrm/contact/view/rel', "action=delete&reset=1&id={$relationship->id}&cid={$relationship->contact_id_a}&rtype={$rType}&context=home" ), - ); + ]; } $title = CRM_Contact_BAO_Contact::displayName($relationship->contact_id_a) . ' (' . CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $relationship->relationship_type_id, 'label_a_b' @@ -404,13 +455,13 @@ public static function loadExistingRelationshipDetails($params) { return $params; } - $fieldsToFill = array('contact_id_a', 'contact_id_b', 'relationship_type_id'); - $result = CRM_Core_DAO::executeQuery("SELECT " . implode(',', $fieldsToFill) . " FROM civicrm_relationship WHERE id = %1", array( - 1 => array( + $fieldsToFill = ['contact_id_a', 'contact_id_b', 'relationship_type_id']; + $result = CRM_Core_DAO::executeQuery("SELECT " . implode(',', $fieldsToFill) . " FROM civicrm_relationship WHERE id = %1", [ + 1 => [ $params['id'], 'Integer', - ), - )); + ], + ]); while ($result->fetch()) { foreach ($fieldsToFill as $field) { $params[$field] = !empty($params[$field]) ? $params[$field] : $result->$field; @@ -429,8 +480,8 @@ public static function loadExistingRelationshipDetails($params) { * @return array * @throws \CRM_Core_Exception */ - public static function setContactABFromIDs($params, $ids = array(), $contactID = NULL) { - $returnFields = array(); + public static function setContactABFromIDs($params, $ids = [], $contactID = NULL) { + $returnFields = []; // $ids['contact'] is deprecated but comes from legacyCreateMultiple function. if (empty($ids['contact'])) { @@ -444,7 +495,7 @@ public static function setContactABFromIDs($params, $ids = array(), $contactID = list($relationshipTypeID, $first) = explode('_', $relationshipTypes); $returnFields['relationship_type_id'] = $relationshipTypeID; - foreach (array('a', 'b') as $contactLetter) { + foreach (['a', 'b'] as $contactLetter) { if (empty($params['contact_' . $contactLetter])) { if ($first == $contactLetter) { $returnFields['contact_id_' . $contactLetter] = CRM_Utils_Array::value('contact', $ids); @@ -466,18 +517,17 @@ public static function setContactABFromIDs($params, $ids = array(), $contactID = * array of defaults for creating relationship */ public static function getdefaults() { - return array( - 'is_active' => 0, - 'is_permission_a_b' => 0, - 'is_permission_b_a' => 0, + return [ + 'is_active' => 1, + 'is_permission_a_b' => self::NONE, + 'is_permission_b_a' => self::NONE, 'description' => '', 'start_date' => 'NULL', 'case_id' => NULL, 'end_date' => 'NULL', - ); + ]; } - /** * Check if there is data to create the object. * @@ -530,7 +580,7 @@ public static function getContactRelationshipType( $onlySubTypeRelationTypes = FALSE ) { - $relationshipType = array(); + $relationshipType = []; $allRelationshipType = CRM_Core_PseudoConstant::relationshipType($column); $otherContactType = NULL; @@ -539,7 +589,7 @@ public static function getContactRelationshipType( $relationship->id = $relationshipId; if ($relationship->find(TRUE)) { $contact = new CRM_Contact_DAO_Contact(); - $contact->id = ($relationship->contact_id_a === $contactId) ? $relationship->contact_id_b : $relationship->contact_id_a; + $contact->id = ($relationship->contact_id_a == $contactId) ? $relationship->contact_id_b : $relationship->contact_id_a; if ($contact->find(TRUE)) { $otherContactType = $contact->contact_type; @@ -651,10 +701,10 @@ public static function clearCurrentEmployer($id, $action) { //to delete relationship between household and individual \ //or between individual and organization if (($action & CRM_Core_Action::DISABLE) || ($action & CRM_Core_Action::DELETE)) { - $relTypes = CRM_Utils_Array::index(array('name_a_b'), CRM_Core_PseudoConstant::relationshipType('name')); + $relTypes = CRM_Utils_Array::index(['name_a_b'], CRM_Core_PseudoConstant::relationshipType('name')); if ( (isset($relTypes['Employee of']) && $relationship->relationship_type_id == $relTypes['Employee of']['id']) || - (isset ($relTypes['Household Member of']) && $relationship->relationship_type_id == $relTypes['Household Member of']['id']) + (isset($relTypes['Household Member of']) && $relationship->relationship_type_id == $relTypes['Household Member of']['id']) ) { $sharedContact = new CRM_Contact_DAO_Contact(); $sharedContact->id = $relationship->contact_id_a; @@ -682,6 +732,7 @@ public static function clearCurrentEmployer($id, $action) { * Relationship id. * * @return null + * @throws \CRM_Core_Exception */ public static function del($id) { // delete from relationship table @@ -691,12 +742,12 @@ public static function del($id) { if (CRM_Core_Permission::access('CiviMember')) { // create $params array which isrequired to delete memberships // of the related contacts. - $params = array( + $params = [ 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b", - 'contact_check' => array($relationship->contact_id_b => 1), - ); + 'contact_check' => [$relationship->contact_id_b => 1], + ]; - $ids = array(); + $ids = []; // calling relatedMemberships to delete the memberships of // related contacts. self::relatedMemberships($relationship->contact_id_a, @@ -713,10 +764,10 @@ public static function del($id) { CRM_Utils_Hook::post('delete', 'Relationship', $id, $relationship); // delete the recently created Relationship - $relationshipRecent = array( + $relationshipRecent = [ 'id' => $id, 'type' => 'Relationship', - ); + ]; CRM_Utils_Recent::del($relationshipRecent); return $relationship; @@ -732,18 +783,20 @@ public static function del($id) { * @param array $params * @param array $ids * @param bool $active + * + * @throws \CRM_Core_Exception */ - public static function disableEnableRelationship($id, $action, $params = array(), $ids = array(), $active = FALSE) { + public static function disableEnableRelationship($id, $action, $params = [], $ids = [], $active = FALSE) { $relationship = self::clearCurrentEmployer($id, $action); if ($id) { // create $params array which is required to delete memberships // of the related contacts. if (empty($params)) { - $params = array( + $params = [ 'relationship_type_id' => "{$relationship->relationship_type_id}_a_b", - 'contact_check' => array($relationship->contact_id_b => 1), - ); + 'contact_check' => [$relationship->contact_id_b => 1], + ]; } $contact_id_a = empty($params['contact_id_a']) ? $relationship->contact_id_a : $params['contact_id_a']; // calling relatedMemberships to delete/add the memberships of @@ -909,7 +962,7 @@ public static function checkDuplicateRelationship(&$params, $id, $contactId = 0, * (I don't think the last 2 support dates but not sure */ - $dateFields = array('end_date', 'start_date'); + $dateFields = ['end_date', 'start_date']; foreach ($dateFields as $dateField) { if (array_key_exists($dateField, $params)) { if (empty($params[$dateField]) || $params[$dateField] == 'null') { @@ -952,11 +1005,9 @@ public static function checkDuplicateRelationship(&$params, $id, $contactId = 0, // Check whether the custom field values are identical. $result = self::checkDuplicateCustomFields($params, $relationship->id); if ($result) { - $relationship->free(); return TRUE; } } - $relationship->free(); return FALSE; } @@ -976,7 +1027,7 @@ private static function checkDuplicateCustomFields(&$params, $relationshipId) { // Get the custom values of the existing relationship. $existingValues = CRM_Core_BAO_CustomValueTable::getEntityValues($relationshipId, 'Relationship'); // Create a similar array for the new relationship. - $newValues = array(); + $newValues = []; if (array_key_exists('custom', $params)) { // $params['custom'] seems to be an array. Each value is again an array. // This array contains one value (key -1), and this value seems to be @@ -1006,9 +1057,9 @@ private static function checkDuplicateCustomFields(&$params, $relationshipId) { * @param bool $is_active * Value we want to set the is_active field. * + * @return bool + * * @throws CiviCRM_API3_Exception - * @return Object - * DAO object on success, null otherwise */ public static function setIsActive($id, $is_active) { // as both the create & add functions have a bunch of logic in them that @@ -1017,11 +1068,11 @@ public static function setIsActive($id, $is_active) { // however, a longer term solution would be to simplify the add, create & api functions // to be more standard. It is debatable @ that point whether it's better to call the BAO // direct as the api is more tested. - $result = civicrm_api('relationship', 'create', array( + $result = civicrm_api('relationship', 'create', [ 'id' => $id, 'is_active' => $is_active, 'version' => 3, - )); + ]); if (is_array($result) && !empty($result['is_error']) && $result['error_message'] != 'Duplicate Relationship') { throw new CiviCRM_API3_Exception($result['error_message'], CRM_Utils_Array::value('error_code', $result, 'undefined'), $result); @@ -1045,7 +1096,7 @@ public static function &getValues(&$params, &$values) { if (empty($params)) { return NULL; } - $v = array(); + $v = []; // get the specific number of relationship or all relationships. if (!empty($params['numRelationship'])) { @@ -1084,8 +1135,9 @@ public static function &getValues(&$params, &$values) { * * @return array * [select, from, where] + * @throws \Exception */ - public static function makeURLClause($contactId, $status, $numRelationship, $count, $relationshipId, $direction, $params = array()) { + public static function makeURLClause($contactId, $status, $numRelationship, $count, $relationshipId, $direction, $params = []) { $select = $from = $where = ''; $select = '( '; @@ -1145,13 +1197,17 @@ public static function makeURLClause($contactId, $status, $numRelationship, $cou else { $from .= 'ON ( civicrm_contact.id = civicrm_relationship.contact_id_b ) '; } - $from .= " + + if (!$count) { + $from .= " LEFT JOIN civicrm_address ON (civicrm_address.contact_id = civicrm_contact.id AND civicrm_address.is_primary = 1) LEFT JOIN civicrm_phone ON (civicrm_phone.contact_id = civicrm_contact.id AND civicrm_phone.is_primary = 1) LEFT JOIN civicrm_email ON (civicrm_email.contact_id = civicrm_contact.id AND civicrm_email.is_primary = 1) LEFT JOIN civicrm_state_province ON (civicrm_address.state_province_id = civicrm_state_province.id) LEFT JOIN civicrm_country ON (civicrm_address.country_id = civicrm_country.id) "; + } + $where = 'WHERE ( 1 )'; if ($contactId) { if ($direction == 'a_b') { @@ -1207,7 +1263,7 @@ public static function makeURLClause($contactId, $status, $numRelationship, $cou $where .= ' ) '; } - return array($select, $from, $where); + return [$select, $from, $where]; } /** @@ -1229,9 +1285,12 @@ public static function makeURLClause($contactId, $status, $numRelationship, $cou * @param bool $permissionedContact * to return only permissioned Contact * @param array $params + * @param bool $includeTotalCount + * Should we return a count of total accessable relationships * * @return array|int * relationship records + * @throws \Exception */ public static function getRelationship( $contactId = NULL, @@ -1239,9 +1298,9 @@ public static function getRelationship( $count = 0, $relationshipId = 0, $links = NULL, $permissionMask = NULL, $permissionedContact = FALSE, - $params = array() + $params = [], $includeTotalCount = FALSE ) { - $values = array(); + $values = []; if (!$contactId && !$relationshipId) { return $values; } @@ -1273,12 +1332,12 @@ public static function getRelationship( } // building the query string - $queryString = $select1 . $from1 . $where1 . $select2 . $from2 . $where2 . $order . $limit; + $queryString = $select1 . $from1 . $where1 . $select2 . $from2 . $where2; $relationship = new CRM_Contact_DAO_Relationship(); - $relationship->query($queryString); - $row = array(); + $relationship->query($queryString . $order . $limit); + $row = []; if ($count) { $relationshipCount = 0; while ($relationship->fetch()) { @@ -1288,6 +1347,10 @@ public static function getRelationship( } else { + if ($includeTotalCount) { + $values['total_relationships'] = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM ({$queryString}) AS r"); + } + $mask = NULL; if ($status != self::INACTIVE) { if ($links) { @@ -1370,14 +1433,14 @@ public static function getRelationship( } if ($links) { - $replace = array( + $replace = [ 'id' => $rid, 'rtype' => $values[$rid]['rtype'], 'cid' => $contactId, 'cbid' => $values[$rid]['cid'], 'caseid' => $values[$rid]['case_id'], 'clientid' => $contactId, - ); + ]; if ($status == self::INACTIVE) { // setting links for inactive relationships @@ -1409,13 +1472,14 @@ public static function getRelationship( if ($hasCaseAccess) { // give access by copying to MAX_ACTION temporarily, otherwise leave at NONE which won't display $links[CRM_Core_Action::MAX_ACTION] = $links[CRM_Core_Action::NONE]; - $links[CRM_Core_Action::MAX_ACTION]['name'] = ts('Manage Case #%1', array(1 => $values[$rid]['case_id'])); + $links[CRM_Core_Action::MAX_ACTION]['name'] = ts('Manage Case #%1', [1 => $values[$rid]['case_id']]); $links[CRM_Core_Action::MAX_ACTION]['class'] = 'no-popup'; // Also make sure we have the right client cid since can get here from multiple relationship tabs. if ($values[$rid]['rtype'] == 'b_a') { $replace['clientid'] = $values[$rid]['cid']; } + $values[$rid]['case'] = ''; } } @@ -1432,7 +1496,6 @@ public static function getRelationship( } } - $relationship->free(); return $values; } } @@ -1446,12 +1509,12 @@ public static function getRelationship( * @return array * array reference of all relationship types with context to current contact type . */ - static public function getRelationType($targetContactType) { - $relationshipType = array(); + public static function getRelationType($targetContactType) { + $relationshipType = []; $allRelationshipType = CRM_Core_PseudoConstant::relationshipType(); foreach ($allRelationshipType as $key => $type) { - if ($type['contact_type_b'] == $targetContactType) { + if ($type['contact_type_b'] == $targetContactType || empty($type['contact_type_b'])) { $relationshipType[$key . '_a_b'] = $type['label_a_b']; } } @@ -1478,12 +1541,13 @@ static public function getRelationType($targetContactType) { * @param bool $active * * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public static function relatedMemberships($contactId, &$params, $ids, $action = CRM_Core_Action::ADD, $active = TRUE) { // Check the end date and set the status of the relationship // accordingly. $status = self::CURRENT; - $targetContact = $targetContact = CRM_Utils_Array::value('contact_check', $params, array()); + $targetContact = $targetContact = CRM_Utils_Array::value('contact_check', $params, []); $today = date('Ymd'); // If a relationship hasn't yet started, just return for now @@ -1517,7 +1581,7 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = // this call is coming from somewhere where the direction was resolved early on (e.g an api call) // so we can assume _a_b $relDirection = "_a_b"; - $targetContact = array($params['contact_id_b'] => 1); + $targetContact = [$params['contact_id_b'] => 1]; } if (($action & CRM_Core_Action::ADD) || @@ -1527,7 +1591,7 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = } elseif ($action & CRM_Core_Action::UPDATE) { $contact = $ids['contact']; - $targetContact = array($ids['contactTarget'] => 1); + $targetContact = [$ids['contactTarget'] => 1]; } // Build the 'values' array for @@ -1536,24 +1600,24 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = // This will allow us to check if either of the contacts in // relationship have active memberships. - $values = array(); + $values = []; // 1. ContactA - $values[$contact] = array( + $values[$contact] = [ 'relatedContacts' => $targetContact, 'relationshipTypeId' => $relTypeId, 'relationshipTypeDirection' => $relDirection, - ); + ]; // 2. ContactB if (!empty($targetContact)) { foreach ($targetContact as $cid => $donCare) { - $values[$cid] = array( - 'relatedContacts' => array($contact => 1), + $values[$cid] = [ + 'relatedContacts' => [$contact => 1], 'relationshipTypeId' => $relTypeId, - ); + ]; - $relTypeParams = array('id' => $relTypeId); - $relTypeValues = array(); + $relTypeParams = ['id' => $relTypeId]; + $relTypeValues = []; CRM_Contact_BAO_RelationshipType::retrieve($relTypeParams, $relTypeValues); if (CRM_Utils_Array::value('name_a_b', $relTypeValues) == CRM_Utils_Array::value('name_b_a', $relTypeValues)) { @@ -1576,7 +1640,7 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = $query .= ' WHERE `is_current_member` = 1 OR `id` = %1 '; } - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($pendingStatusId, 'Integer'))); + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$pendingStatusId, 'Integer']]); while ($dao->fetch()) { $membershipStatusRecordIds[$dao->id] = $dao->id; @@ -1586,8 +1650,8 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = // If contact have any valid membership(s), then add it to // 'values' array. foreach ($values as $cid => $subValues) { - $memParams = array('contact_id' => $cid); - $memberships = array(); + $memParams = ['contact_id' => $cid]; + $memberships = []; // CRM-15829 UPDATES // Since we want PENDING memberships as well, the $active flag needs to be set to false so that this will return all memberships and we can then filter the memberships based on the status IDs recieved above. @@ -1630,27 +1694,21 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = continue; } - $relatedContacts = array_keys(CRM_Utils_Array::value('relatedContacts', $details, array())); + $relatedContacts = array_keys(CRM_Utils_Array::value('relatedContacts', $details, [])); $mainRelatedContactId = reset($relatedContacts); foreach ($details['memberships'] as $membershipId => $membershipValues) { - $relTypeIds = array(); + $membershipInherittedFromContactID = NULL; + if (!empty($membershipValues['owner_membership_id'])) { + // Use get not getsingle so that we get e-notice noise but not a fatal is the membership has already been deleted. + $inheritedFromMembership = civicrm_api3('Membership', 'get', ['id' => $membershipValues['owner_membership_id'], 'sequential' => 1])['values'][0]; + $membershipInherittedFromContactID = (int) $inheritedFromMembership['contact_id']; + } + $relTypeIds = []; if ($action & CRM_Core_Action::DELETE) { - // Delete memberships of the related contacts only if relationship type exists for membership type - $query = " -SELECT relationship_type_id, relationship_direction - FROM civicrm_membership_type - WHERE id = {$membershipValues['membership_type_id']}"; - $dao = CRM_Core_DAO::executeQuery($query); - $relTypeDirs = array(); - while ($dao->fetch()) { - $relTypeId = $dao->relationship_type_id; - $relDirection = $dao->relationship_direction; - } - $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $relTypeId); - if (in_array($values[$cid]['relationshipTypeId'], $relTypeIds - //CRM-16300 check if owner membership exist for related membership - ) && !empty($membershipValues['owner_membership_id']) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) { + // @todo don't return relTypeId here - but it seems to be used later in a cryptic way (hint cryptic is not a complement). + list($relTypeId, $isDeletable) = self::isInheritedMembershipInvalidated($membershipValues, $values, $cid, $mainRelatedContactId); + if ($isDeletable) { CRM_Member_BAO_Membership::deleteRelatedMemberships($membershipValues['owner_membership_id'], $membershipValues['membership_contact_id']); } continue; @@ -1673,7 +1731,7 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = // Get the Membership Type Details. $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($membershipValues['membership_type_id']); // Check if contact's relationship type exists in membership type - $relTypeDirs = array(); + $relTypeDirs = []; if (!empty($membershipType['relationship_type_id'])) { $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_type_id']); } @@ -1702,11 +1760,7 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = $membershipValues['status_id'] = $deceasedStatusId; $membershipValues['skipStatusCal'] = TRUE; } - foreach (array( - 'join_date', - 'start_date', - 'end_date', - ) as $dateField) { + foreach (['join_date', 'start_date', 'end_date'] as $dateField) { if (!empty($membershipValues[$dateField])) { $membershipValues[$dateField] = CRM_Utils_Date::processDate($membershipValues[$dateField]); } @@ -1724,17 +1778,14 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = //contact before creating new membership record. CRM_Member_BAO_Membership::deleteRelatedMemberships($membershipId, $relatedContactId); } - - // check whether we have some related memberships still available - $query = " -SELECT count(*) - FROM civicrm_membership - LEFT JOIN civicrm_membership_status ON (civicrm_membership_status.id = civicrm_membership.status_id) - WHERE membership_type_id = {$membershipValues['membership_type_id']} AND owner_membership_id = {$membershipValues['owner_membership_id']} - AND is_current_member = 1"; - $result = CRM_Core_DAO::singleValueQuery($query); - if ($result < CRM_Utils_Array::value('max_related', $membershipValues, PHP_INT_MAX)) { - CRM_Member_BAO_Membership::create($membershipValues, CRM_Core_DAO::$_nullArray); + //skip status calculation for pay later memberships. + if (!empty($membershipValues['status_id']) && $membershipValues['status_id'] == $pendingStatusId) { + $membershipValues['skipStatusCal'] = TRUE; + } + // As long as the membership itself was not created by inheritance from the same contact + // that stands to inherit the membership we add an inherited membership. + if ($membershipInherittedFromContactID !== (int) $membershipValues['contact_id']) { + $membershipValues = self::addInheritedMembership($membershipValues); } } } @@ -1746,18 +1797,18 @@ public static function relatedMemberships($contactId, &$params, $ids, $action = // previous relationship -- CRM-12078. // CRM-16087 we need to pass ownerMembershipId to isRelatedMembershipExpired function if (empty($params['relationship_ids']) && !empty($params['id'])) { - $relIds = array($params['id']); + $relIds = [$params['id']]; } else { $relIds = CRM_Utils_Array::value('relationship_ids', $params); } if (self::isRelatedMembershipExpired($relTypeIds, $contactId, $mainRelatedContactId, $relTypeId, - $relIds) && !empty($membershipValues['owner_membership_id'] - ) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) { + $relIds) && !empty($membershipValues['owner_membership_id'] + ) && !empty($values[$mainRelatedContactId]['memberships'][$membershipValues['owner_membership_id']])) { $membershipValues['status_id'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus', 'Expired', 'id', 'label'); $type = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $membershipValues['membership_type_id'], 'name', 'id'); CRM_Member_BAO_Membership::add($membershipValues); - CRM_Core_Session::setStatus(ts("Inherited membership {$type} status was changed to Expired due to the change in relationship type."), ts('Record Updated'), 'alert'); + CRM_Core_Session::setStatus(ts("Inherited membership %1 status was changed to Expired due to the change in relationship type.", [1 => $type]), ts('Record Updated'), 'alert'); } } } @@ -1788,10 +1839,10 @@ public static function isRelatedMembershipExpired($membershipTypeRelationshipTyp return FALSE; } - $relParamas = array( - 1 => array($contactId, 'Integer'), - 2 => array($mainRelatedContactId, 'Integer'), - ); + $relParamas = [ + 1 => [$contactId, 'Integer'], + 2 => [$mainRelatedContactId, 'Integer'], + ]; if ($contactId == $mainRelatedContactId) { $recordsFound = (int) CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM civicrm_relationship WHERE relationship_type_id IN ( " . implode(',', $membershipTypeRelationshipTypeIDs) . " ) AND @@ -1830,7 +1881,7 @@ public static function getCurrentEmployer($contactIds) { "; $dao = CRM_Core_DAO::executeQuery($query); - $currentEmployer = array(); + $currentEmployer = []; while ($dao->fetch()) { $currentEmployer[$dao->id]['org_id'] = $dao->employer_id; $currentEmployer[$dao->id]['org_name'] = $dao->organization_name; @@ -1853,20 +1904,20 @@ public static function getCurrentEmployer($contactIds) { * Array of contacts */ public static function getPermissionedContacts($contactID, $relTypeId = NULL, $name = NULL, $contactType = NULL) { - $contacts = array(); - $args = array(1 => array($contactID, 'Integer')); + $contacts = []; + $args = [1 => [$contactID, 'Integer']]; $relationshipTypeClause = $contactTypeClause = ''; if ($relTypeId) { // @todo relTypeId is only ever passed in as an int. Change this to reflect that - // probably being overly conservative by not doing so but working on stable release. $relationshipTypeClause = 'AND cr.relationship_type_id IN (%2) '; - $args[2] = array($relTypeId, 'String'); + $args[2] = [$relTypeId, 'String']; } if ($contactType) { $contactTypeClause = ' AND cr.relationship_type_id = crt.id AND crt.contact_type_b = %3 '; - $args[3] = array($contactType, 'String'); + $args[3] = [$contactType, 'String']; } $query = " @@ -1891,10 +1942,10 @@ public static function getPermissionedContacts($contactID, $relTypeId = NULL, $n $dao = CRM_Core_DAO::executeQuery($query, $args); while ($dao->fetch()) { - $contacts[$dao->id] = array( + $contacts[$dao->id] = [ 'name' => $dao->name, 'value' => $dao->id, - ); + ]; } return $contacts; @@ -1948,6 +1999,7 @@ public static function mergeRelationships($mainId, $otherId, &$sqls) { * * @return bool * True on success, false if error is encountered. + * @throws \CiviCRM_API3_Exception */ public static function disableExpiredRelationships() { $query = "SELECT id FROM civicrm_relationship WHERE is_active = 1 AND end_date < CURDATE()"; @@ -1975,19 +2027,20 @@ public static function disableExpiredRelationships() { * @param null $direction * * @return array|void + * @throws \CiviCRM_API3_Exception */ public static function membershipTypeToRelationshipTypes(&$params, $direction = NULL) { - $membershipType = civicrm_api3('membership_type', 'getsingle', array( + $membershipType = civicrm_api3('membership_type', 'getsingle', [ 'id' => $params['membership_type_id'], 'return' => 'relationship_type_id, relationship_direction', - )); + ]); $relationshipTypes = $membershipType['relationship_type_id']; if (empty($relationshipTypes)) { return NULL; } // if we don't have any contact data we can only filter on type if (empty($params['contact_id']) && empty($params['contact_id_a']) && empty($params['contact_id_a'])) { - $params['relationship_type_id'] = array('IN' => $relationshipTypes); + $params['relationship_type_id'] = ['IN' => $relationshipTypes]; return NULL; } else { @@ -2003,7 +2056,7 @@ public static function membershipTypeToRelationshipTypes(&$params, $direction = } } if (!empty($types)) { - $params['relationship_type_id'] = array('IN' => $types); + $params['relationship_type_id'] = ['IN' => $types]; } elseif (!empty($clauses)) { return explode(' OR ', $clauses); @@ -2015,7 +2068,6 @@ public static function membershipTypeToRelationshipTypes(&$params, $direction = } } - /** * Wrapper for contact relationship selector. * @@ -2024,6 +2076,7 @@ public static function membershipTypeToRelationshipTypes(&$params, $direction = * * @return array * associated array of contact relationships + * @throws \Exception */ public static function getContactRelationshipSelector(&$params) { // format the params @@ -2043,7 +2096,7 @@ public static function getContactRelationshipSelector(&$params) { // check logged in user for permission $page = new CRM_Core_Page(); CRM_Contact_Page_View::checkUserPermission($page, $params['contact_id']); - $permissions = array($page->_permission); + $permissions = [$page->_permission]; if ($page->_permission == CRM_Core_Permission::EDIT) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -2063,24 +2116,19 @@ public static function getContactRelationshipSelector(&$params) { $params['rp'], 0, 0, $links, $mask, $permissionedContacts, - $params + $params, TRUE ); - $contactRelationships = array(); - $params['total'] = 0; + $contactRelationships = []; + $params['total'] = $relationships['total_relationships']; + unset($relationships['total_relationships']); if (!empty($relationships)) { - // FIXME: we cannot directly determine total permissioned relationship, hence re-fire query - $permissionedRelationships = CRM_Contact_BAO_Relationship::getRelationship($params['contact_id'], - $relationshipStatus, - 0, 0, 0, - NULL, NULL, - $permissionedContacts - ); - $params['total'] = count($permissionedRelationships); + + $displayName = CRM_Contact_BAO_Contact::displayName($params['contact_id']); // format params foreach ($relationships as $relationshipId => $values) { - $relationship = array(); + $relationship = []; $relationship['DT_RowId'] = $values['id']; $relationship['DT_RowClass'] = 'crm-entity'; @@ -2088,7 +2136,7 @@ public static function getContactRelationshipSelector(&$params) { $relationship['DT_RowClass'] .= ' disabled'; } - $relationship['DT_RowAttr'] = array(); + $relationship['DT_RowAttr'] = []; $relationship['DT_RowAttr']['data-entity'] = 'relationship'; $relationship['DT_RowAttr']['data-id'] = $values['id']; @@ -2103,29 +2151,49 @@ public static function getContactRelationshipSelector(&$params) { 'civicrm/contact/view', "reset=1&cid={$values['cid']}"); - $relationship['relation'] = CRM_Utils_System::href( - $values['relation'], - 'civicrm/contact/view/rel', - "action=view&reset=1&cid={$values['cid']}&id={$values['id']}&rtype={$values['rtype']}"); - - if ($params['context'] == 'current') { - if (($params['contact_id'] == $values['contact_id_a'] AND $values['is_permission_a_b'] == 1) OR - ($params['contact_id'] == $values['contact_id_b'] AND $values['is_permission_b_a'] == 1) - ) { - $relationship['sort_name'] .= ' *'; - } - - if (($values['cid'] == $values['contact_id_a'] AND $values['is_permission_a_b'] == 1) OR - ($values['cid'] == $values['contact_id_b'] AND $values['is_permission_b_a'] == 1) - ) { - $relationship['relation'] .= ' *'; - } - } + $relationship['relation'] = CRM_Utils_Array::value('case', $values, '') . CRM_Utils_System::href( + $values['relation'], + 'civicrm/contact/view/rel', + "action=view&reset=1&cid={$values['cid']}&id={$values['id']}&rtype={$values['rtype']}"); if (!empty($values['description'])) { $relationship['relation'] .= "

    {$values['description']}

    "; } + if ($params['context'] == 'current') { + $smarty = CRM_Core_Smarty::singleton(); + + $contactCombos = [ + [ + 'permContact' => $params['contact_id'], + 'permDisplayName' => $displayName, + 'otherContact' => $values['cid'], + 'otherDisplayName' => $values['display_name'], + 'columnKey' => 'sort_name', + ], + [ + 'permContact' => $values['cid'], + 'permDisplayName' => $values['display_name'], + 'otherContact' => $params['contact_id'], + 'otherDisplayName' => $displayName, + 'columnKey' => 'relation', + ], + ]; + + foreach ($contactCombos as $combo) { + foreach ([CRM_Contact_BAO_Relationship::EDIT, CRM_Contact_BAO_Relationship::VIEW] as $permType) { + $smarty->assign('permType', $permType); + if (($combo['permContact'] == $values['contact_id_a'] and $values['is_permission_a_b'] == $permType) + || ($combo['permContact'] == $values['contact_id_b'] and $values['is_permission_b_a'] == $permType) + ) { + $smarty->assign('permDisplayName', $combo['permDisplayName']); + $smarty->assign('otherDisplayName', $combo['otherDisplayName']); + $relationship[$combo['columnKey']] .= $smarty->fetch('CRM/Contact/Page/View/RelationshipPerm.tpl'); + } + } + } + } + $relationship['start_date'] = CRM_Utils_Date::customFormat($values['start_date']); $relationship['end_date'] = CRM_Utils_Date::customFormat($values['end_date']); $relationship['city'] = $values['city']; @@ -2138,7 +2206,11 @@ public static function getContactRelationshipSelector(&$params) { } } - $relationshipsDT = array(); + $columnHeaders = self::getColumnHeaders(); + $selector = NULL; + CRM_Utils_Hook::searchColumns('relationship.rows', $columnHeaders, $contactRelationships, $selector); + + $relationshipsDT = []; $relationshipsDT['data'] = $contactRelationships; $relationshipsDT['recordsTotal'] = $params['total']; $relationshipsDT['recordsFiltered'] = $params['total']; @@ -2146,4 +2218,230 @@ public static function getContactRelationshipSelector(&$params) { return $relationshipsDT; } + /** + * @return array + */ + public static function getColumnHeaders() { + return [ + 'relation' => [ + 'name' => ts('Relationship'), + 'sort' => 'relation', + 'direction' => CRM_Utils_Sort::ASCENDING, + ], + 'sort_name' => [ + 'name' => '', + 'sort' => 'sort_name', + 'direction' => CRM_Utils_Sort::ASCENDING, + ], + 'start_date' => [ + 'name' => ts('Start'), + 'sort' => 'start_date', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'end_date' => [ + 'name' => ts('End'), + 'sort' => 'end_date', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'city' => [ + 'name' => ts('City'), + 'sort' => 'city', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'state' => [ + 'name' => ts('State/Prov'), + 'sort' => 'state', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'email' => [ + 'name' => ts('Email'), + 'sort' => 'email', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'phone' => [ + 'name' => ts('Phone'), + 'sort' => 'phone', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + 'links' => [ + 'name' => '', + 'sort' => 'links', + 'direction' => CRM_Utils_Sort::DONTCARE, + ], + ]; + } + + /** + * @inheritdoc + */ + public static function buildOptions($fieldName, $context = NULL, $props = []) { + if ($fieldName === 'relationship_type_id') { + return self::buildRelationshipTypeOptions($props); + } + + return parent::buildOptions($fieldName, $context, $props); + } + + /** + * Builds a list of options available for relationship types + * + * @param array $params + * - contact_type: Limits by contact type on the "A" side + * - relationship_id: Used to find the value for contact type for "B" side. + * If contact_a matches provided contact_id then type of contact_b will + * be used. Otherwise uses type of contact_a. Must be used with contact_id + * - contact_id: Limits by contact types of this contact on the "A" side + * - is_form: Returns array with keys indexed for use in a quickform + * - relationship_direction: For relationship types with duplicate names + * on both sides, defines which option should be returned, a_b or b_a + * + * @return array + */ + public static function buildRelationshipTypeOptions($params = []) { + $contactId = CRM_Utils_Array::value('contact_id', $params); + $direction = CRM_Utils_Array::value('relationship_direction', $params, 'a_b'); + $relationshipId = CRM_Utils_Array::value('relationship_id', $params); + $contactType = CRM_Utils_Array::value('contact_type', $params); + $isForm = CRM_Utils_Array::value('is_form', $params); + $showAll = FALSE; + + // getContactRelationshipType will return an empty set if these are not set + if (!$contactId && !$relationshipId && !$contactType) { + $showAll = TRUE; + } + + $labels = self::getContactRelationshipType( + $contactId, + $direction, + $relationshipId, + $contactType, + $showAll, + 'label' + ); + + if ($isForm) { + return $labels; + } + + $names = self::getContactRelationshipType( + $contactId, + $direction, + $relationshipId, + $contactType, + $showAll, + 'name' + ); + + // ensure $names contains only entries in $labels + $names = array_intersect_key($names, $labels); + + $nameToLabels = array_combine($names, $labels); + + return $nameToLabels; + } + + /** + * Process the params from api, form and check if current + * employer should be set or unset. + * + * @param array $params + * @param int $relationshipId + * @param int|null $updatedRelTypeID + * + * @return bool + * TRUE if current employer needs to be cleared. + * @throws \CiviCRM_API3_Exception + */ + public static function isCurrentEmployerNeedingToBeCleared($params, $relationshipId, $updatedRelTypeID = NULL) { + $existingTypeID = (int) CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Relationship', $relationshipId, 'relationship_type_id'); + $updatedRelTypeID = $updatedRelTypeID ? $updatedRelTypeID : $existingTypeID; + $currentEmployerID = (int) civicrm_api3('Contact', 'getvalue', ['return' => 'current_employer_id', 'id' => $params['contact_id_a']]); + + if ($currentEmployerID !== (int) $params['contact_id_b'] || !self::isRelationshipTypeCurrentEmployer($existingTypeID)) { + return FALSE; + } + //Clear employer if relationship is expired. + if (!empty($params['end_date']) && strtotime($params['end_date']) < time()) { + return TRUE; + } + //current employer checkbox is disabled on the form. + //inactive or relationship type(employer of) is updated. + if ((isset($params['is_current_employer']) && empty($params['is_current_employer'])) + || ((isset($params['is_active']) && empty($params['is_active']))) + || $existingTypeID != $updatedRelTypeID) { + // If there are no other active employer relationships between the same 2 contacts... + if (!civicrm_api3('Relationship', 'getcount', [ + 'is_active' => 1, + 'relationship_type_id' => $existingTypeID, + 'id' => ['<>' => $params['id']], + 'contact_id_a' => $params['contact_id_a'], + 'contact_id_b' => $params['contact_id_b'], + ])) { + return TRUE; + } + } + + return FALSE; + } + + /** + * Is this a current employer relationship type. + * + * @todo - this could use cached pseudoconstant lookups. + * + * @param int $existingTypeID + * + * @return bool + */ + private static function isRelationshipTypeCurrentEmployer(int $existingTypeID): bool { + $isCurrentEmployerRelationshipType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $existingTypeID, 'name_b_a') === 'Employer of'; + return $isCurrentEmployerRelationshipType; + } + + /** + * Is the inherited relationship invalidated by this relationship change. + * + * @param $membershipValues + * @param array $values + * @param int $cid + * @param int $mainRelatedContactId + * + * @return array + * @throws \CiviCRM_API3_Exception + */ + private static function isInheritedMembershipInvalidated($membershipValues, array $values, $cid, $mainRelatedContactId): array { + $membershipType = CRM_Member_BAO_MembershipType::getMembershipType($membershipValues['membership_type_id']); + $relTypeIds = $membershipType['relationship_type_id']; + $membshipInheritedFrom = $membershipValues['owner_membership_id'] ?? NULL; + if (!$membshipInheritedFrom || !in_array($values[$cid]['relationshipTypeId'], $relTypeIds)) { + return [implode(',', $relTypeIds), FALSE]; + } + //CRM-16300 check if owner membership exist for related membership + $isDeletable = !empty($values[$mainRelatedContactId]['memberships'][$membshipInheritedFrom]); + return [implode(',', $relTypeIds), $isDeletable]; + } + + /** + * Add an inherited membership, provided max related not exceeded. + * + * @param array $membershipValues + * + * @return array + * @throws \CRM_Core_Exception + */ + protected static function addInheritedMembership($membershipValues) { + $query = " +SELECT count(*) + FROM civicrm_membership + LEFT JOIN civicrm_membership_status ON (civicrm_membership_status.id = civicrm_membership.status_id) + WHERE membership_type_id = {$membershipValues['membership_type_id']} + AND owner_membership_id = {$membershipValues['owner_membership_id']} + AND is_current_member = 1"; + $result = CRM_Core_DAO::singleValueQuery($query); + if ($result < CRM_Utils_Array::value('max_related', $membershipValues, PHP_INT_MAX)) { + CRM_Member_BAO_Membership::create($membershipValues); + } + return $membershipValues; + } + } diff --git a/CRM/Contact/BAO/RelationshipType.php b/CRM/Contact/BAO/RelationshipType.php index 656f7bae6ef5..bc11bf2f0248 100644 --- a/CRM/Contact/BAO/RelationshipType.php +++ b/CRM/Contact/BAO/RelationshipType.php @@ -1,9 +1,9 @@ copyValues($params); if ($relationshipType->find(TRUE)) { CRM_Core_DAO::storeValues($relationshipType, $defaults); - $relationshipType->free(); return $relationshipType; } return NULL; @@ -68,8 +67,8 @@ public static function retrieve(&$params, &$defaults) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { return CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_RelationshipType', $id, 'is_active', $is_active); @@ -79,25 +78,20 @@ public static function setIsActive($id, $is_active) { * Add the relationship type in the db. * * @param array $params - * (reference ) an assoc array of name/value pairs. - * @param array $ids - * The array that holds all the db ids. * * @return CRM_Contact_DAO_RelationshipType */ - public static function add(&$params, &$ids) { - //to change name, CRM-3336 - if (empty($params['label_a_b']) && !empty($params['name_a_b'])) { - $params['label_a_b'] = $params['name_a_b']; - } - - if (empty($params['label_b_a']) && !empty($params['name_b_a'])) { - $params['label_b_a'] = $params['name_b_a']; - } + public static function add($params) { + if (empty($params['id'])) { + // Set name to label if not set + if (empty($params['label_a_b']) && !empty($params['name_a_b'])) { + $params['label_a_b'] = $params['name_a_b']; + } + if (empty($params['label_b_a']) && !empty($params['name_b_a'])) { + $params['label_b_a'] = $params['name_b_a']; + } - // set label to name if it's not set - but *only* for - // ADD action. CRM-3336 as part from (CRM-3522) - if (empty($ids['relationshipType'])) { + // set label to name if it's not set if (empty($params['name_a_b']) && !empty($params['label_a_b'])) { $params['name_a_b'] = $params['label_a_b']; } @@ -109,24 +103,18 @@ public static function add(&$params, &$ids) { // action is taken depending upon the mode $relationshipType = new CRM_Contact_DAO_RelationshipType(); - $relationshipType->copyValues($params); + $hook = empty($params['id']) ? 'create' : 'edit'; + CRM_Utils_Hook::pre($hook, 'RelationshipType', CRM_Utils_Array::value('id', $params), $params); - // if label B to A is blank, insert the value label A to B for it - if (!strlen(trim($strName = CRM_Utils_Array::value('name_b_a', $params)))) { - $relationshipType->name_b_a = CRM_Utils_Array::value('name_a_b', $params); - } - if (!strlen(trim($strName = CRM_Utils_Array::value('label_b_a', $params)))) { - $relationshipType->label_b_a = CRM_Utils_Array::value('label_a_b', $params); - } - - $relationshipType->id = CRM_Utils_Array::value('relationshipType', $ids); + $relationshipType->copyValues($params); + $relationshipType->save(); - $result = $relationshipType->save(); + CRM_Utils_Hook::post($hook, 'RelationshipType', $relationshipType->id, $relationshipType); CRM_Core_PseudoConstant::relationshipType('label', TRUE); CRM_Core_PseudoConstant::relationshipType('name', TRUE); CRM_Case_XMLProcessor::flushStaticCaches(); - return $result; + return $relationshipType; } /** @@ -153,10 +141,10 @@ public static function del($relationshipTypeId) { $relationship->delete(); // remove this relationship type from membership types - $mems = civicrm_api3('MembershipType', 'get', array( - 'relationship_type_id' => array('LIKE' => "%{$relationshipTypeId}%"), - 'return' => array('id', 'relationship_type_id', 'relationship_direction'), - )); + $mems = civicrm_api3('MembershipType', 'get', [ + 'relationship_type_id' => ['LIKE' => "%{$relationshipTypeId}%"], + 'return' => ['id', 'relationship_type_id', 'relationship_direction'], + ]); foreach ($mems['values'] as $membershipTypeId => $membershipType) { $pos = array_search($relationshipTypeId, $membershipType['relationship_type_id']); // Api call may have returned false positives but currently the relationship_type_id uses diff --git a/CRM/Contact/BAO/SavedSearch.php b/CRM/Contact/BAO/SavedSearch.php index 694f849847db..396cb3826ae5 100644 --- a/CRM/Contact/BAO/SavedSearch.php +++ b/CRM/Contact/BAO/SavedSearch.php @@ -1,9 +1,9 @@ 'event_date_low', + 'event_end_date_high' => 'event_date_high', + 'case_from_start_date_low' => 'case_from_date_low', + 'case_from_start_date_high' => 'case_from_date_high', + 'case_to_end_date_low' => 'case_to_date_low', + 'case_to_end_date_high' => 'case_to_date_high', + ]; + $fv = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $id, 'form_values'); $result = NULL; if ($fv) { @@ -99,7 +108,7 @@ public static function getFormValues($id) { $result = unserialize($fv); } - $specialFields = array('contact_type', 'group', 'contact_tags', 'member_membership_type_id', 'member_status_id'); + $specialFields = ['contact_type', 'group', 'contact_tags', 'member_membership_type_id', 'member_status_id']; foreach ($result as $element => $value) { if (CRM_Contact_BAO_Query::isAlreadyProcessedForQueryFormat($value)) { $id = CRM_Utils_Array::value(0, $value); @@ -107,21 +116,36 @@ public static function getFormValues($id) { if (is_array($value) && in_array(key($value), CRM_Core_DAO::acceptedSQLOperators(), TRUE)) { $op = key($value); $value = CRM_Utils_Array::value($op, $value); - if (in_array($op, array('BETWEEN', '>=', '<='))) { + if (in_array($op, ['BETWEEN', '>=', '<='])) { self::decodeRelativeFields($result, $id, $op, $value); unset($result[$element]); continue; } } + // Check for a date range field, which might be a standard date + // range or a relative date. if (strpos($id, '_date_low') !== FALSE || strpos($id, '_date_high') !== FALSE) { $entityName = strstr($id, '_date', TRUE); - if (!empty($result['relative_dates']) && array_key_exists($entityName, $result['relative_dates'])) { - $result[$id] = NULL; - $result["{$entityName}_date_relative"] = $result['relative_dates'][$entityName]; - } - else { - $result[$id] = $value; - $result["{$entityName}_date_relative"] = 0; + + // This is the default, for non relative dates. We will overwrite + // it if we determine this is a relative date. + $result[$id] = $value; + $result["{$entityName}_date_relative"] = 0; + + if (!empty($result['relative_dates'])) { + if (array_key_exists($entityName, $result['relative_dates'])) { + // We have a match from a regular field. + $result[$id] = NULL; + $result["{$entityName}_date_relative"] = $result['relative_dates'][$entityName]; + } + elseif (!empty($specialDateFields[$id])) { + // We may have a match on a special date field. + $entityName = strstr($specialDateFields[$id], '_date', TRUE); + if (array_key_exists($entityName, $result['relative_dates'])) { + $result[$id] = NULL; + $result["{$entityName}_relative"] = $result['relative_dates'][$entityName]; + } + } } } else { @@ -172,7 +196,7 @@ public static function getFormValues($id) { unset($result['privacy']['do_not_toggle']); } - $result['privacy_options'] = array(); + $result['privacy_options'] = []; foreach ($result['privacy'] as $name => $val) { if ($val) { $result['privacy_options'][] = $name; @@ -183,6 +207,13 @@ public static function getFormValues($id) { } } + if ($customSearchClass = CRM_Utils_Array::value('customSearchClass', $result)) { + // check if there is a special function - formatSavedSearchFields defined in the custom search form + if (method_exists($customSearchClass, 'formatSavedSearchFields')) { + $customSearchClass::formatSavedSearchFields($result); + } + } + return $result; } @@ -246,7 +277,7 @@ public static function contactIDsSQL($id) { return CRM_Contact_BAO_SearchCustom::contactIDSQL(NULL, $id); } else { - $tables = $whereTables = array('civicrm_contact' => 1); + $tables = $whereTables = ['civicrm_contact' => 1]; $where = CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables); if (!$where) { $where = '( 1 )'; @@ -274,10 +305,10 @@ public static function fromWhereEmail($id) { return CRM_Contact_BAO_SearchCustom::fromWhereEmail(NULL, $id); } else { - $tables = $whereTables = array('civicrm_contact' => 1, 'civicrm_email' => 1); + $tables = $whereTables = ['civicrm_contact' => 1, 'civicrm_email' => 1]; $where = CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables); $from = CRM_Contact_BAO_Query::fromClause($whereTables); - return array($from, $where); + return [$from, $where]; } } else { @@ -289,45 +320,8 @@ public static function fromWhereEmail($id) { $where = " ( 1 ) "; $tables['civicrm_contact'] = $whereTables['civicrm_contact'] = 1; $tables['civicrm_email'] = $whereTables['civicrm_email'] = 1; - return array($from, $where); - } - } - - /** - * Given a saved search compute the clause and the tables and store it for future use. - */ - public function buildClause() { - $fv = unserialize($this->form_values); - - if ($this->mapping_id) { - $params = CRM_Core_BAO_Mapping::formattedFields($fv); + return [$from, $where]; } - else { - $params = CRM_Contact_BAO_Query::convertFormValues($fv); - } - - if (!empty($params)) { - $tables = $whereTables = array(); - $this->where_clause = CRM_Contact_BAO_Query::getWhereClause($params, NULL, $tables, $whereTables); - if (!empty($tables)) { - $this->select_tables = serialize($tables); - } - if (!empty($whereTables)) { - $this->where_tables = serialize($whereTables); - } - } - } - - /** - * Save the search. - * - * @param bool $hook - */ - public function save($hook = TRUE) { - // first build the computed fields - $this->buildClause(); - - parent::save($hook); } /** @@ -389,7 +383,7 @@ protected function assignTestValue($fieldName, &$fieldDef, $counter) { if ($fieldName == 'form_values') { // A dummy value for form_values. $this->{$fieldName} = serialize( - array('sort_name' => "SortName{$counter}")); + ['sort_name' => "SortName{$counter}"]); } else { parent::assignTestValues($fieldName, $fieldDef, $counter); @@ -403,10 +397,29 @@ protected function assignTestValue($fieldName, &$fieldDef, $counter) { * @param array $formValues */ public static function saveRelativeDates(&$queryParams, $formValues) { - $relativeDates = array('relative_dates' => array()); + // This is required only until all fields are converted to datepicker fields as the new format is truer to the + // form format and simply saves (e.g) custom_3_relative => "this.year" + $relativeDates = ['relative_dates' => []]; + $specialDateFields = [ + 'event_relative', + 'case_from_relative', + 'case_to_relative', + 'participant_relative', + 'log_date_relative', + 'birth_date_relative', + 'deceased_date_relative', + 'mailing_date_relative', + 'relation_date_relative', + 'relation_start_date_relative', + 'relation_end_date_relative', + 'relation_action_date_relative', + ]; foreach ($formValues as $id => $value) { - if (preg_match('/_date_relative$/', $id) && !empty($value)) { + if (in_array($id, $specialDateFields) && !empty($value)) { $entityName = strstr($id, '_date', TRUE); + if (empty($entityName)) { + $entityName = strstr($id, '_relative', TRUE); + } $relativeDates['relative_dates'][$entityName] = $value; } } @@ -426,14 +439,15 @@ public static function saveRelativeDates(&$queryParams, $formValues) { */ public static function saveSkippedElement(&$queryParams, $formValues) { // these are elements which are skipped in a smart group criteria - $specialElements = array( + $specialElements = [ 'operator', 'component_mode', 'display_relationship_type', - ); + 'uf_group_id', + ]; foreach ($specialElements as $element) { if (!empty($formValues[$element])) { - $queryParams[] = array($element, '=', $formValues[$element], 0, 0); + $queryParams[] = [$element, '=', $formValues[$element], 0, 0]; } } } @@ -453,7 +467,12 @@ public static function decodeRelativeFields(&$formValues, $fieldName, $op, $valu // select date range as default if ($isCustomDateField) { - $formValues[$fieldName . '_relative'] = 0; + if (array_key_exists('relative_dates', $formValues) && array_key_exists($fieldName, $formValues['relative_dates'])) { + $formValues[$fieldName . '_relative'] = $formValues['relative_dates'][$fieldName]; + } + else { + $formValues[$fieldName . '_relative'] = 0; + } } switch ($op) { case 'BETWEEN': diff --git a/CRM/Contact/BAO/SearchCustom.php b/CRM/Contact/BAO/SearchCustom.php index e13bc52c4f14..cadd086e4c40 100644 --- a/CRM/Contact/BAO/SearchCustom.php +++ b/CRM/Contact/BAO/SearchCustom.php @@ -1,9 +1,9 @@ 'custom_search', 'return' => 'name', 'value' => $customSearchID, - )); + ]); $ext = CRM_Extension_System::singleton()->getMapper(); @@ -95,7 +95,7 @@ public static function details($csID, $ssID = NULL, $gID = NULL) { CRM_Core_Error::fatal('Custom search file: ' . $customSearchFile . ' does not exist. Please verify your custom search settings in CiviCRM administrative panel.'); } - return array($customSearchID, $customSearchClass, $formValues); + return [$customSearchID, $customSearchClass, $formValues]; } /** @@ -138,7 +138,7 @@ public static function &buildFormValues($args) { $args = trim($args); $values = explode("\n", $args); - $formValues = array(); + $formValues = []; foreach ($values as $value) { list($n, $v) = CRM_Utils_System::explode('=', $value, 2); if (!empty($v)) { @@ -160,7 +160,7 @@ public static function fromWhereEmail($csID, $ssID) { $from = $customClass->from(); $where = $customClass->where(); - return array($from, $where); + return [$from, $where]; } } diff --git a/CRM/Contact/BAO/SubscriptionHistory.php b/CRM/Contact/BAO/SubscriptionHistory.php index 331e2add53fb..ec1a44d18991 100644 --- a/CRM/Contact/BAO/SubscriptionHistory.php +++ b/CRM/Contact/BAO/SubscriptionHistory.php @@ -1,9 +1,9 @@ __table = 'civicrm_acl_contact_cache'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'user_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('ACL Contact Cache ID') , - 'description' => 'primary key', - 'required' => true, + 'title' => ts('ACL Contact Cache ID'), + 'description' => ts('primary key'), + 'required' => TRUE, + 'where' => 'civicrm_acl_contact_cache.id', 'table_name' => 'civicrm_acl_contact_cache', 'entity' => 'ACLContactCache', 'bao' => 'CRM_Contact_DAO_ACLContactCache', 'localizable' => 0, - ) , - 'user_id' => array( + ], + 'user_id' => [ 'name' => 'user_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to civicrm_contact (could be null for anon user)', + 'title' => ts('Contact ID'), + 'description' => ts('FK to civicrm_contact (could be null for anon user)'), + 'where' => 'civicrm_acl_contact_cache.user_id', 'table_name' => 'civicrm_acl_contact_cache', 'entity' => 'ACLContactCache', 'bao' => 'CRM_Contact_DAO_ACLContactCache', 'localizable' => 0, - 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to civicrm_contact', - 'required' => true, + 'title' => ts('Contact ID'), + 'description' => ts('FK to civicrm_contact'), + 'required' => TRUE, + 'where' => 'civicrm_acl_contact_cache.contact_id', 'table_name' => 'civicrm_acl_contact_cache', 'entity' => 'ACLContactCache', 'bao' => 'CRM_Contact_DAO_ACLContactCache', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'operation' => array( + ], + 'operation' => [ 'name' => 'operation', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Operation') , - 'description' => 'What operation does this user have permission on?', - 'required' => true, + 'title' => ts('Operation'), + 'description' => ts('What operation does this user have permission on?'), + 'required' => TRUE, 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_acl_contact_cache.operation', 'table_name' => 'civicrm_acl_contact_cache', 'entity' => 'ACLContactCache', 'bao' => 'CRM_Contact_DAO_ACLContactCache', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_ACL_BAO_ACL::operation', - ) - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -197,10 +187,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_contact_cache', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'acl_contact_cache', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -208,27 +199,33 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_contact_cache', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'acl_contact_cache', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_user_contact_operation' => array( + $indices = [ + 'UI_user_contact_operation' => [ 'name' => 'UI_user_contact_operation', - 'field' => array( + 'field' => [ 0 => 'user_id', 1 => 'contact_id', 2 => 'operation', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_acl_contact_cache::1::user_id::contact_id::operation', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/Contact.php b/CRM/Contact/DAO/Contact.php index ab724755b13d..a7da4e718e99 100644 --- a/CRM/Contact/DAO/Contact.php +++ b/CRM/Contact/DAO/Contact.php @@ -1,1394 +1,1424 @@ __table = 'civicrm_contact'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'primary_contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'employer_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'primary_contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'employer_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'Unique Contact ID', - 'required' => true, - 'import' => true, + 'title' => ts('Contact ID'), + 'description' => ts('Unique Contact ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contact.id', 'headerPattern' => '/internal|contact?|id$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'contact_type' => array( + ], + 'contact_type' => [ 'name' => 'contact_type', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type') , - 'description' => 'Type of Contact.', + 'title' => ts('Contact Type'), + 'description' => ts('Type of Contact.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, - 'export' => true, 'where' => 'civicrm_contact.contact_type', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NULL', - ) - ) , - 'contact_sub_type' => array( + ], + ], + 'contact_sub_type' => [ 'name' => 'contact_sub_type', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Subtype') , - 'description' => 'May be used to over-ride contact view and edit templates.', + 'title' => ts('Contact Subtype'), + 'description' => ts('May be used to over-ride contact view and edit templates.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.contact_sub_type', 'headerPattern' => '/C(ontact )?(subtype|sub-type|sub type)/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND, + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NOT NULL', - ) - ) , - 'do_not_email' => array( + ], + ], + 'do_not_email' => [ 'name' => 'do_not_email', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Do Not Email') , - 'import' => true, + 'title' => ts('Do Not Email'), + 'import' => TRUE, 'where' => 'civicrm_contact.do_not_email', 'headerPattern' => '/d(o )?(not )?(email)/i', 'dataPattern' => '/^\d{1,}$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'do_not_phone' => array( + ], + ], + 'do_not_phone' => [ 'name' => 'do_not_phone', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Do Not Phone') , - 'import' => true, + 'title' => ts('Do Not Phone'), + 'import' => TRUE, 'where' => 'civicrm_contact.do_not_phone', 'headerPattern' => '/d(o )?(not )?(call|phone)/i', 'dataPattern' => '/^\d{1,}$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'do_not_mail' => array( + ], + ], + 'do_not_mail' => [ 'name' => 'do_not_mail', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Do Not Mail') , - 'import' => true, + 'title' => ts('Do Not Mail'), + 'import' => TRUE, 'where' => 'civicrm_contact.do_not_mail', 'headerPattern' => '/^(d(o\s)?n(ot\s)?mail)|(\w*)?bulk\s?(\w*)$/i', 'dataPattern' => '/^\d{1,}$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'do_not_sms' => array( + ], + ], + 'do_not_sms' => [ 'name' => 'do_not_sms', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Do Not Sms') , - 'import' => true, + 'title' => ts('Do Not Sms'), + 'import' => TRUE, 'where' => 'civicrm_contact.do_not_sms', 'headerPattern' => '/d(o )?(not )?(sms)/i', 'dataPattern' => '/^\d{1,}$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'do_not_trade' => array( + ], + ], + 'do_not_trade' => [ 'name' => 'do_not_trade', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Do Not Trade') , - 'import' => true, + 'title' => ts('Do Not Trade'), + 'import' => TRUE, 'where' => 'civicrm_contact.do_not_trade', 'headerPattern' => '/d(o )?(not )?(trade)/i', 'dataPattern' => '/^\d{1,}$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'is_opt_out' => array( + ], + ], + 'is_opt_out' => [ 'name' => 'is_opt_out', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('No Bulk Emails (User Opt Out)') , - 'description' => 'Has the contact opted out from receiving all bulk email from the organization or site domain?', - 'required' => true, - 'import' => true, + 'title' => ts('No Bulk Emails (User Opt Out)'), + 'description' => ts('Has the contact opted out from receiving all bulk email from the organization or site domain?'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contact.is_opt_out', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'legal_identifier' => array( + ], + ], + 'legal_identifier' => [ 'name' => 'legal_identifier', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Legal Identifier') , - 'description' => 'May be used for SSN, EIN/TIN, Household ID (census) or other applicable unique legal/government ID. - ', + 'title' => ts('Legal Identifier'), + 'description' => ts('May be used for SSN, EIN/TIN, Household ID (census) or other applicable unique legal/government ID.'), 'maxlength' => 32, 'size' => CRM_Utils_Type::MEDIUM, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.legal_identifier', 'headerPattern' => '/legal\s?id/i', 'dataPattern' => '/\w+?\d{5,}/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'external_identifier' => array( + ], + ], + 'external_identifier' => [ 'name' => 'external_identifier', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('External Identifier') , - 'description' => 'Unique trusted external ID (generally from a legacy app/datasource). Particularly useful for deduping operations.', + 'title' => ts('External Identifier'), + 'description' => ts('Unique trusted external ID (generally from a legacy app/datasource). Particularly useful for deduping operations.'), 'maxlength' => 64, 'size' => 8, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.external_identifier', 'headerPattern' => '/external\s?id/i', 'dataPattern' => '/^\d{11,}$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'sort_name' => array( + ], + ], + 'sort_name' => [ 'name' => 'sort_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Sort Name') , - 'description' => 'Name used for sorting different contact types', + 'title' => ts('Sort Name'), + 'description' => ts('Name used for sorting different contact types'), 'maxlength' => 128, 'size' => 30, - 'export' => true, 'where' => 'civicrm_contact.sort_name', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'display_name' => array( + ], + ], + 'display_name' => [ 'name' => 'display_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Display Name') , - 'description' => 'Formatted name representing preferred format for display/print/other output.', + 'title' => ts('Display Name'), + 'description' => ts('Formatted name representing preferred format for display/print/other output.'), 'maxlength' => 128, 'size' => 30, - 'export' => true, 'where' => 'civicrm_contact.display_name', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'nick_name' => array( + ], + ], + 'nick_name' => [ 'name' => 'nick_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Nickname') , - 'description' => 'Nickname.', + 'title' => ts('Nickname'), + 'description' => ts('Nickname.'), 'maxlength' => 128, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.nick_name', 'headerPattern' => '/n(ick\s)name|nick$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'legal_name' => array( + ], + ], + 'legal_name' => [ 'name' => 'legal_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Legal Name') , - 'description' => 'Legal Name.', + 'title' => ts('Legal Name'), + 'description' => ts('Legal Name.'), 'maxlength' => 128, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.legal_name', 'headerPattern' => '/^legal|(l(egal\s)?name)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'image_URL' => array( + ], + ], + 'image_URL' => [ 'name' => 'image_URL', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Image Url') , - 'description' => 'optional URL for preferred image (photo, logo, etc.) to display for this contact.', - 'import' => true, + 'title' => ts('Image Url'), + 'description' => ts('optional URL for preferred image (photo, logo, etc.) to display for this contact.'), + 'import' => TRUE, 'where' => 'civicrm_contact.image_URL', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'File', - ) , - ) , - 'preferred_communication_method' => array( + ], + ], + 'preferred_communication_method' => [ 'name' => 'preferred_communication_method', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Preferred Communication Method') , - 'description' => 'What is the preferred mode of communication.', + 'title' => ts('Preferred Communication Method'), + 'description' => ts('What is the preferred mode of communication.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.preferred_communication_method', 'headerPattern' => '/^p(ref\w*\s)?c(omm\w*)|( meth\w*)$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND, + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'preferred_communication_method', 'optionEditPath' => 'civicrm/admin/options/preferred_communication_method', - ) - ) , - 'preferred_language' => array( + ], + ], + 'preferred_language' => [ 'name' => 'preferred_language', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Preferred Language') , - 'description' => 'Which language is preferred for communication. FK to languages in civicrm_option_value.', + 'title' => ts('Preferred Language'), + 'description' => ts('Which language is preferred for communication. FK to languages in civicrm_option_value.'), 'maxlength' => 5, 'size' => CRM_Utils_Type::SIX, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.preferred_language', 'headerPattern' => '/^lang/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'languages', 'keyColumn' => 'name', 'optionEditPath' => 'civicrm/admin/options/languages', - ) - ) , - 'preferred_mail_format' => array( + ], + ], + 'preferred_mail_format' => [ 'name' => 'preferred_mail_format', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Preferred Mail Format') , - 'description' => 'What is the preferred mode of sending an email.', + 'title' => ts('Preferred Mail Format'), + 'description' => ts('What is the preferred mode of sending an email.'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.preferred_mail_format', 'headerPattern' => '/^p(ref\w*\s)?m(ail\s)?f(orm\w*)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => 'Both', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::pmf', - ) - ) , - 'hash' => array( + ], + ], + 'hash' => [ 'name' => 'hash', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Hash') , - 'description' => 'Key for validating requests related to this contact.', + 'title' => ts('Contact Hash'), + 'description' => ts('Key for validating requests related to this contact.'), 'maxlength' => 32, 'size' => CRM_Utils_Type::MEDIUM, - 'export' => true, 'where' => 'civicrm_contact.hash', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'api_key' => array( + ], + 'api_key' => [ 'name' => 'api_key', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Api Key') , - 'description' => 'API Key for validating requests related to this contact.', + 'title' => ts('Api Key'), + 'description' => ts('API Key for validating requests related to this contact.'), 'maxlength' => 32, 'size' => CRM_Utils_Type::MEDIUM, + 'where' => 'civicrm_contact.api_key', + 'permission' => [ + [ + 'administer CiviCRM', + 'edit api keys', + ], + ], 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'contact_source' => array( + ], + 'contact_source' => [ 'name' => 'source', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Source') , - 'description' => 'where contact come from, e.g. import, donate module insert...', + 'title' => ts('Contact Source'), + 'description' => ts('where contact come from, e.g. import, donate module insert...'), 'maxlength' => 255, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.source', 'headerPattern' => '/(C(ontact\s)?Source)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'first_name' => array( + ], + ], + 'first_name' => [ 'name' => 'first_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('First Name') , - 'description' => 'First Name.', + 'title' => ts('First Name'), + 'description' => ts('First Name.'), 'maxlength' => 64, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.first_name', 'headerPattern' => '/^first|(f(irst\s)?name)$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'middle_name' => array( + ], + ], + 'middle_name' => [ 'name' => 'middle_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Middle Name') , - 'description' => 'Middle Name.', + 'title' => ts('Middle Name'), + 'description' => ts('Middle Name.'), 'maxlength' => 64, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.middle_name', 'headerPattern' => '/^middle|(m(iddle\s)?name)$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'last_name' => array( + ], + ], + 'last_name' => [ 'name' => 'last_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Last Name') , - 'description' => 'Last Name.', + 'title' => ts('Last Name'), + 'description' => ts('Last Name.'), 'maxlength' => 64, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.last_name', 'headerPattern' => '/^last|(l(ast\s)?name)$/i', 'dataPattern' => '/^\w+(\s\w+)?+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'prefix_id' => array( + ], + ], + 'prefix_id' => [ 'name' => 'prefix_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Individual Prefix') , - 'description' => 'Prefix or Title for name (Ms, Mr...). FK to prefix ID', - 'import' => true, + 'title' => ts('Individual Prefix'), + 'description' => ts('Prefix or Title for name (Ms, Mr...). FK to prefix ID'), + 'import' => TRUE, 'where' => 'civicrm_contact.prefix_id', 'headerPattern' => '/^(prefix|title)/i', 'dataPattern' => '/^(mr|ms|mrs|sir|dr)\.?$/i', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'individual_prefix', 'optionEditPath' => 'civicrm/admin/options/individual_prefix', - ) - ) , - 'suffix_id' => array( + ], + ], + 'suffix_id' => [ 'name' => 'suffix_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Individual Suffix') , - 'description' => 'Suffix for name (Jr, Sr...). FK to suffix ID', - 'import' => true, + 'title' => ts('Individual Suffix'), + 'description' => ts('Suffix for name (Jr, Sr...). FK to suffix ID'), + 'import' => TRUE, 'where' => 'civicrm_contact.suffix_id', 'headerPattern' => '/^suffix$/i', 'dataPattern' => '/^(sr|jr)\.?|i{2,}$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'individual_suffix', 'optionEditPath' => 'civicrm/admin/options/individual_suffix', - ) - ) , - 'formal_title' => array( + ], + ], + 'formal_title' => [ 'name' => 'formal_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Formal Title') , - 'description' => 'Formal (academic or similar) title in front of name. (Prof., Dr. etc.)', + 'title' => ts('Formal Title'), + 'description' => ts('Formal (academic or similar) title in front of name. (Prof., Dr. etc.)'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.formal_title', 'headerPattern' => '/^title/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'communication_style_id' => array( + ], + ], + 'communication_style_id' => [ 'name' => 'communication_style_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Communication Style') , - 'description' => 'Communication style (e.g. formal vs. familiar) to use with this contact. FK to communication styles in civicrm_option_value.', - 'export' => true, + 'title' => ts('Communication Style'), + 'description' => ts('Communication style (e.g. formal vs. familiar) to use with this contact. FK to communication styles in civicrm_option_value.'), + 'import' => TRUE, 'where' => 'civicrm_contact.communication_style_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'headerPattern' => '/style/i', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'communication_style', 'optionEditPath' => 'civicrm/admin/options/communication_style', - ) - ) , - 'email_greeting_id' => array( + ], + ], + 'email_greeting_id' => [ 'name' => 'email_greeting_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Email Greeting ID') , - 'description' => 'FK to civicrm_option_value.id, that has to be valid registered Email Greeting.', + 'title' => ts('Email Greeting ID'), + 'description' => ts('FK to civicrm_option_value.id, that has to be valid registered Email Greeting.'), + 'where' => 'civicrm_contact.email_greeting_id', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'email_greeting_custom' => array( + 'pseudoconstant' => [ + 'optionGroupName' => 'email_greeting', + 'optionEditPath' => 'civicrm/admin/options/email_greeting', + ], + ], + 'email_greeting_custom' => [ 'name' => 'email_greeting_custom', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Email Greeting Custom') , - 'description' => 'Custom Email Greeting.', + 'title' => ts('Email Greeting Custom'), + 'description' => ts('Custom Email Greeting.'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.email_greeting_custom', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => false, + 'export' => FALSE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'email_greeting_display' => array( + ], + ], + 'email_greeting_display' => [ 'name' => 'email_greeting_display', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Email Greeting') , - 'description' => 'Cache Email Greeting.', + 'title' => ts('Email Greeting'), + 'description' => ts('Cache Email Greeting.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contact.email_greeting_display', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'postal_greeting_id' => array( + ], + ], + 'postal_greeting_id' => [ 'name' => 'postal_greeting_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Postal Greeting ID') , - 'description' => 'FK to civicrm_option_value.id, that has to be valid registered Postal Greeting.', + 'title' => ts('Postal Greeting ID'), + 'description' => ts('FK to civicrm_option_value.id, that has to be valid registered Postal Greeting.'), + 'where' => 'civicrm_contact.postal_greeting_id', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'postal_greeting_custom' => array( + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'postal_greeting', + 'optionEditPath' => 'civicrm/admin/options/postal_greeting', + ], + ], + 'postal_greeting_custom' => [ 'name' => 'postal_greeting_custom', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Postal Greeting Custom') , - 'description' => 'Custom Postal greeting.', + 'title' => ts('Postal Greeting Custom'), + 'description' => ts('Custom Postal greeting.'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.postal_greeting_custom', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => false, + 'export' => FALSE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'postal_greeting_display' => array( + ], + ], + 'postal_greeting_display' => [ 'name' => 'postal_greeting_display', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Postal Greeting') , - 'description' => 'Cache Postal greeting.', + 'title' => ts('Postal Greeting'), + 'description' => ts('Cache Postal greeting.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contact.postal_greeting_display', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'addressee_id' => array( + ], + ], + 'addressee_id' => [ 'name' => 'addressee_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Addressee ID') , - 'description' => 'FK to civicrm_option_value.id, that has to be valid registered Addressee.', + 'title' => ts('Addressee ID'), + 'description' => ts('FK to civicrm_option_value.id, that has to be valid registered Addressee.'), + 'where' => 'civicrm_contact.addressee_id', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'addressee_custom' => array( + 'pseudoconstant' => [ + 'optionGroupName' => 'addressee', + 'optionEditPath' => 'civicrm/admin/options/addressee', + ], + ], + 'addressee_custom' => [ 'name' => 'addressee_custom', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Addressee Custom') , - 'description' => 'Custom Addressee.', + 'title' => ts('Addressee Custom'), + 'description' => ts('Custom Addressee.'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.addressee_custom', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => false, + 'export' => FALSE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'addressee_display' => array( + ], + ], + 'addressee_display' => [ 'name' => 'addressee_display', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Addressee') , - 'description' => 'Cache Addressee.', + 'title' => ts('Addressee'), + 'description' => ts('Cache Addressee.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contact.addressee_display', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'job_title' => array( + ], + ], + 'job_title' => [ 'name' => 'job_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Job Title') , - 'description' => 'Job Title', + 'title' => ts('Job Title'), + 'description' => ts('Job Title'), 'maxlength' => 255, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.job_title', 'headerPattern' => '/^job|(j(ob\s)?title)$/i', 'dataPattern' => '//', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'gender_id' => array( + ], + ], + 'gender_id' => [ 'name' => 'gender_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Gender') , - 'description' => 'FK to gender ID', - 'import' => true, + 'title' => ts('Gender'), + 'description' => ts('FK to gender ID'), + 'import' => TRUE, 'where' => 'civicrm_contact.gender_id', 'headerPattern' => '/^gender$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'gender', 'optionEditPath' => 'civicrm/admin/options/gender', - ) - ) , - 'birth_date' => array( + ], + ], + 'birth_date' => [ 'name' => 'birth_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Birth Date') , - 'description' => 'Date of birth', - 'import' => true, + 'title' => ts('Birth Date'), + 'description' => ts('Date of birth'), + 'import' => TRUE, 'where' => 'civicrm_contact.birth_date', 'headerPattern' => '/^birth|(b(irth\s)?date)|D(\W*)O(\W*)B(\W*)$/i', 'dataPattern' => '/\d{4}-?\d{2}-?\d{2}/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'birth', - ) , - ) , - 'is_deceased' => array( + ], + ], + 'is_deceased' => [ 'name' => 'is_deceased', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Deceased') , - 'import' => true, + 'title' => ts('Deceased'), + 'import' => TRUE, 'where' => 'civicrm_contact.is_deceased', 'headerPattern' => '/i(s\s)?d(eceased)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'deceased_date' => array( + ], + ], + 'deceased_date' => [ 'name' => 'deceased_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Deceased Date') , - 'description' => 'Date of deceased', - 'import' => true, + 'title' => ts('Deceased Date'), + 'description' => ts('Date of deceased'), + 'import' => TRUE, 'where' => 'civicrm_contact.deceased_date', 'headerPattern' => '/^deceased|(d(eceased\s)?date)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'birth', - ) , - ) , - 'household_name' => array( + ], + ], + 'household_name' => [ 'name' => 'household_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Household Name') , - 'description' => 'Household Name.', + 'title' => ts('Household Name'), + 'description' => ts('Household Name.'), 'maxlength' => 128, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.household_name', 'headerPattern' => '/^household|(h(ousehold\s)?name)$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'primary_contact_id' => array( + ], + ], + 'primary_contact_id' => [ 'name' => 'primary_contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Household Primary Contact ID') , - 'description' => 'Optional FK to Primary Contact for this household.', + 'title' => ts('Household Primary Contact ID'), + 'description' => ts('Optional FK to Primary Contact for this household.'), + 'where' => 'civicrm_contact.primary_contact_id', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - ) , - 'organization_name' => array( + ], + ], + 'organization_name' => [ 'name' => 'organization_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Organization Name') , - 'description' => 'Organization Name.', + 'title' => ts('Organization Name'), + 'description' => ts('Organization Name.'), 'maxlength' => 128, 'size' => 30, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.organization_name', 'headerPattern' => '/^organization|(o(rganization\s)?name)$/i', 'dataPattern' => '/^\w+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'sic_code' => array( + ], + ], + 'sic_code' => [ 'name' => 'sic_code', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Sic Code') , - 'description' => 'Standard Industry Classification Code.', + 'title' => ts('Sic Code'), + 'description' => ts('Standard Industry Classification Code.'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.sic_code', 'headerPattern' => '/^sic|(s(ic\s)?code)$/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'user_unique_id' => array( + ], + ], + 'user_unique_id' => [ 'name' => 'user_unique_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Unique ID (OpenID)') , - 'description' => 'the OpenID (or OpenID-style http://username.domain/) unique identifier for this contact mainly used for logging in to CiviCRM', + 'title' => ts('Unique ID (OpenID)'), + 'description' => ts('the OpenID (or OpenID-style http://username.domain/) unique identifier for this contact mainly used for logging in to CiviCRM'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contact.user_unique_id', 'headerPattern' => '/^Open\s?ID|u(niq\w*)?\s?ID/i', 'dataPattern' => '/^[\w\/\:\.]+$/', - 'export' => true, + 'export' => TRUE, 'rule' => 'url', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'current_employer_id' => array( + ], + ], + 'current_employer_id' => [ 'name' => 'employer_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Current Employer') , - 'description' => 'OPTIONAL FK to civicrm_contact record.', - 'export' => true, + 'title' => ts('Current Employer'), + 'description' => ts('OPTIONAL FK to civicrm_contact record.'), 'where' => 'civicrm_contact.employer_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => array( + 'html' => [ 'type' => 'EntityRef', - ) , - ) , - 'contact_is_deleted' => array( + ], + ], + 'contact_is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Contact is in Trash') , - 'required' => true, - 'export' => true, + 'title' => ts('Contact is in Trash'), + 'required' => TRUE, 'where' => 'civicrm_contact.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'created_date' => array( + ], + ], + 'created_date' => [ 'name' => 'created_date', 'type' => CRM_Utils_Type::T_TIMESTAMP, - 'title' => ts('Created Date') , - 'description' => 'When was the contact was created.', - 'required' => false, - 'export' => true, + 'title' => ts('Created Date'), + 'description' => ts('When was the contact was created.'), + 'required' => FALSE, 'where' => 'civicrm_contact.created_date', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'default' => 'NULL', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - 'modified_date' => array( + ], + 'modified_date' => [ 'name' => 'modified_date', 'type' => CRM_Utils_Type::T_TIMESTAMP, - 'title' => ts('Modified Date') , - 'description' => 'When was the contact (or closely related entity) was created or modified or deleted.', - 'required' => false, - 'export' => true, + 'title' => ts('Modified Date'), + 'description' => ts('When was the contact (or closely related entity) was created or modified or deleted.'), + 'required' => FALSE, 'where' => 'civicrm_contact.modified_date', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'default' => 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', 'table_name' => 'civicrm_contact', 'entity' => 'Contact', 'bao' => 'CRM_Contact_BAO_Contact', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -1396,10 +1426,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contact', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contact', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -1407,147 +1438,177 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contact', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contact', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_contact_type' => array( + $indices = [ + 'index_contact_type' => [ 'name' => 'index_contact_type', - 'field' => array( + 'field' => [ 0 => 'contact_type', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::contact_type', - ) , - 'index_contact_sub_type' => array( + ], + 'index_contact_sub_type' => [ 'name' => 'index_contact_sub_type', - 'field' => array( + 'field' => [ 0 => 'contact_sub_type', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::contact_sub_type', - ) , - 'UI_external_identifier' => array( + ], + 'UI_external_identifier' => [ 'name' => 'UI_external_identifier', - 'field' => array( + 'field' => [ 0 => 'external_identifier', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contact::1::external_identifier', - ) , - 'index_sort_name' => array( + ], + 'index_sort_name' => [ 'name' => 'index_sort_name', - 'field' => array( + 'field' => [ 0 => 'sort_name', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::sort_name', - ) , - 'index_preferred_communication_method' => array( + ], + 'index_preferred_communication_method' => [ 'name' => 'index_preferred_communication_method', - 'field' => array( + 'field' => [ 0 => 'preferred_communication_method', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::preferred_communication_method', - ) , - 'index_hash' => array( + ], + 'index_hash' => [ 'name' => 'index_hash', - 'field' => array( + 'field' => [ 0 => 'hash', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::hash', - ) , - 'index_api_key' => array( + ], + 'index_api_key' => [ 'name' => 'index_api_key', - 'field' => array( + 'field' => [ 0 => 'api_key', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::api_key', - ) , - 'index_first_name' => array( + ], + 'index_first_name' => [ 'name' => 'index_first_name', - 'field' => array( + 'field' => [ 0 => 'first_name', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::first_name', - ) , - 'index_last_name' => array( + ], + 'index_last_name' => [ 'name' => 'index_last_name', - 'field' => array( + 'field' => [ 0 => 'last_name', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::last_name', - ) , - 'UI_prefix' => array( + ], + 'UI_prefix' => [ 'name' => 'UI_prefix', - 'field' => array( + 'field' => [ 0 => 'prefix_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::prefix_id', - ) , - 'UI_suffix' => array( + ], + 'UI_suffix' => [ 'name' => 'UI_suffix', - 'field' => array( + 'field' => [ 0 => 'suffix_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::suffix_id', - ) , - 'index_communication_style_id' => array( + ], + 'index_communication_style_id' => [ 'name' => 'index_communication_style_id', - 'field' => array( + 'field' => [ 0 => 'communication_style_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::communication_style_id', - ) , - 'UI_gender' => array( + ], + 'UI_gender' => [ 'name' => 'UI_gender', - 'field' => array( + 'field' => [ 0 => 'gender_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::gender_id', - ) , - 'index_household_name' => array( + ], + 'index_is_deceased' => [ + 'name' => 'index_is_deceased', + 'field' => [ + 0 => 'is_deceased', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_contact::0::is_deceased', + ], + 'index_household_name' => [ 'name' => 'index_household_name', - 'field' => array( + 'field' => [ 0 => 'household_name', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::household_name', - ) , - 'index_organization_name' => array( + ], + 'index_organization_name' => [ 'name' => 'index_organization_name', - 'field' => array( + 'field' => [ 0 => 'organization_name', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::organization_name', - ) , - 'index_is_deleted_sort_name' => array( + ], + 'index_is_deleted_sort_name' => [ 'name' => 'index_is_deleted_sort_name', - 'field' => array( + 'field' => [ 0 => 'is_deleted', 1 => 'sort_name', 2 => 'id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contact::0::is_deleted::sort_name::id', - ) , - ); + ], + 'index_created_date' => [ + 'name' => 'index_created_date', + 'field' => [ + 0 => 'created_date', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_contact::0::created_date', + ], + 'index_modified_date' => [ + 'name' => 'index_modified_date', + 'field' => [ + 0 => 'modified_date', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_contact::0::modified_date', + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/ContactType.php b/CRM/Contact/DAO/ContactType.php index efc21bcb8e6e..b6db4bf85612 100644 --- a/CRM/Contact/DAO/ContactType.php +++ b/CRM/Contact/DAO/ContactType.php @@ -1,264 +1,264 @@ __table = 'civicrm_contact_type'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'parent_id', 'civicrm_contact_type', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'parent_id', 'civicrm_contact_type', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact Type ID') , - 'description' => 'Contact Type ID', - 'required' => true, + 'title' => ts('Contact Type ID'), + 'description' => ts('Contact Type ID'), + 'required' => TRUE, + 'where' => 'civicrm_contact_type.id', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, - ) , - 'name' => array( + ], + 'name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Name') , - 'description' => 'Internal name of Contact Type (or Subtype).', + 'title' => ts('Name'), + 'description' => ts('Internal name of Contact Type (or Subtype).'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_contact_type.name', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, - ) , - 'label' => array( + ], + 'label' => [ 'name' => 'label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type Label') , - 'description' => 'localized Name of Contact Type.', + 'title' => ts('Contact Type Label'), + 'description' => ts('localized Name of Contact Type.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_contact_type.label', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 1, - ) , - 'description' => array( + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Contact Type Description') , - 'description' => 'localized Optional verbose description of the type.', + 'title' => ts('Contact Type Description'), + 'description' => ts('localized Optional verbose description of the type.'), 'rows' => 2, 'cols' => 60, + 'where' => 'civicrm_contact_type.description', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'TextArea', - ) , - ) , - 'image_URL' => array( + ], + ], + 'image_URL' => [ 'name' => 'image_URL', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type Image URL') , - 'description' => 'URL of image if any.', + 'title' => ts('Contact Type Image URL'), + 'description' => ts('URL of image if any.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contact_type.image_URL', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, - ) , - 'parent_id' => array( + ], + 'parent_id' => [ 'name' => 'parent_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact Type Parent') , - 'description' => 'Optional FK to parent contact type.', + 'title' => ts('Contact Type Parent'), + 'description' => ts('Optional FK to parent contact type.'), + 'where' => 'civicrm_contact_type.parent_id', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_ContactType', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'id', 'labelColumn' => 'label', 'condition' => 'parent_id IS NULL', - ) - ) , - 'is_active' => array( + ], + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Contact Type Is Active?') , - 'description' => 'Is this entry active?', + 'title' => ts('Contact Type Is Active?'), + 'description' => ts('Is this entry active?'), + 'where' => 'civicrm_contact_type.is_active', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, - ) , - 'is_reserved' => array( + ], + 'is_reserved' => [ 'name' => 'is_reserved', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Contact Type is Reserved?') , - 'description' => 'Is this contact type a predefined system type', + 'title' => ts('Contact Type is Reserved?'), + 'description' => ts('Is this contact type a predefined system type'), + 'where' => 'civicrm_contact_type.is_reserved', 'table_name' => 'civicrm_contact_type', 'entity' => 'ContactType', 'bao' => 'CRM_Contact_BAO_ContactType', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -266,10 +266,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contact_type', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contact_type', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -277,25 +278,31 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contact_type', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contact_type', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'contact_type' => array( + $indices = [ + 'contact_type' => [ 'name' => 'contact_type', - 'field' => array( + 'field' => [ 0 => 'name', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contact_type::1::name', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/DashboardContact.php b/CRM/Contact/DAO/DashboardContact.php index 951be6206121..e9456cec9e11 100644 --- a/CRM/Contact/DAO/DashboardContact.php +++ b/CRM/Contact/DAO/DashboardContact.php @@ -1,217 +1,215 @@ __table = 'civicrm_dashboard_contact'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'dashboard_id', 'civicrm_dashboard', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'dashboard_id', 'civicrm_dashboard', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Dashboard Contact ID') , - 'required' => true, + 'title' => ts('Dashboard Contact ID'), + 'required' => TRUE, + 'where' => 'civicrm_dashboard_contact.id', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, - ) , - 'dashboard_id' => array( + ], + 'dashboard_id' => [ 'name' => 'dashboard_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Dashboard') , - 'description' => 'Dashboard ID', - 'required' => true, + 'title' => ts('Dashboard'), + 'description' => ts('Dashboard ID'), + 'required' => TRUE, + 'where' => 'civicrm_dashboard_contact.dashboard_id', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_Dashboard', - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Dashboard Contact') , - 'description' => 'Contact ID', - 'required' => true, + 'title' => ts('Dashboard Contact'), + 'description' => ts('Contact ID'), + 'required' => TRUE, + 'where' => 'civicrm_dashboard_contact.contact_id', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'column_no' => array( + ], + 'column_no' => [ 'name' => 'column_no', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Column No') , - 'description' => 'column no for this widget', + 'title' => ts('Column No'), + 'description' => ts('column no for this widget'), + 'where' => 'civicrm_dashboard_contact.column_no', + 'default' => '0', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Dashlet is Active?') , - 'description' => 'Is this widget active?', + 'title' => ts('Dashlet is Active?'), + 'description' => ts('Is this widget active?'), + 'where' => 'civicrm_dashboard_contact.is_active', + 'default' => '0', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, - ) , - 'weight' => array( + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Order') , - 'description' => 'Ordering of the widgets.', + 'title' => ts('Order'), + 'description' => ts('Ordering of the widgets.'), + 'where' => 'civicrm_dashboard_contact.weight', + 'default' => '0', 'table_name' => 'civicrm_dashboard_contact', 'entity' => 'DashboardContact', 'bao' => 'CRM_Contact_BAO_DashboardContact', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -219,10 +217,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dashboard_contact', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'dashboard_contact', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -230,26 +229,32 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dashboard_contact', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'dashboard_contact', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_dashboard_id_contact_id' => array( + $indices = [ + 'index_dashboard_id_contact_id' => [ 'name' => 'index_dashboard_id_contact_id', - 'field' => array( + 'field' => [ 0 => 'dashboard_id', 1 => 'contact_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_dashboard_contact::1::dashboard_id::contact_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/Factory.php b/CRM/Contact/DAO/Factory.php index 5f4432c2b50f..8af2fc1ac416 100644 --- a/CRM/Contact/DAO/Factory.php +++ b/CRM/Contact/DAO/Factory.php @@ -5,7 +5,7 @@ */ class CRM_Contact_DAO_Factory { - static $_classes = array( + public static $_classes = [ 'Address' => 'data', 'Contact' => 'data', 'Email' => 'data', @@ -17,39 +17,35 @@ class CRM_Contact_DAO_Factory { 'Organization' => 'data', 'Phone' => 'data', 'Relationship' => 'data', - ); + ]; - static $_prefix = array( - 'business' => 'CRM/Contact/BAO/', - 'data' => 'CRM/Contact/DAO/', - ); - - static $_suffix = '.php'; + public static $_prefix = [ + 'business' => 'CRM_Contact_BAO_', + 'data' => 'CRM_Contact_DAO_', + ]; /** * @param string $className * * @return mixed */ - static function &create($className) { + public static function create($className) { $type = CRM_Utils_Array::value($className, self::$_classes); if (!$type) { return CRM_Core_DAO_Factory::create($className); } - $file = self::$_prefix[$type] . $className; - $class = str_replace('/', '_', $file); - - require_once($file . self::$_suffix); + $class = self::$_prefix[$type] . $className; if ($type == 'singleton') { $newObj = $class::singleton(); } else { // this is either 'business' or 'data' - $newObj = new $class; + $newObj = new $class(); } return $newObj; } + } diff --git a/CRM/Contact/DAO/Group.php b/CRM/Contact/DAO/Group.php index f3a4ad3091aa..8e7ce46ae841 100644 --- a/CRM/Contact/DAO/Group.php +++ b/CRM/Contact/DAO/Group.php @@ -1,469 +1,504 @@ __table = 'civicrm_group'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'saved_search_id', 'civicrm_saved_search', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'created_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'modified_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'saved_search_id', 'civicrm_saved_search', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'created_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'modified_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group ID') , - 'description' => 'Group ID', - 'required' => true, + 'title' => ts('Group ID'), + 'description' => ts('Group ID'), + 'required' => TRUE, + 'where' => 'civicrm_group.id', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'name' => array( + ], + 'name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Name') , - 'description' => 'Internal name of Group.', + 'title' => ts('Group Name'), + 'description' => ts('Internal name of Group.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_group.name', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'title' => array( + ], + 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Title') , - 'description' => 'Name of Group.', + 'title' => ts('Group Title'), + 'description' => ts('Name of Group.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_group.title', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 1, - ) , - 'description' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Group Description') , - 'description' => 'Optional verbose description of the group.', + 'title' => ts('Group Description'), + 'description' => ts('Optional verbose description of the group.'), 'rows' => 2, 'cols' => 60, + 'where' => 'civicrm_group.description', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'TextArea', - ) , - ) , - 'source' => array( + ], + ], + 'source' => [ 'name' => 'source', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Source') , - 'description' => 'Module or process which created this group.', + 'title' => ts('Group Source'), + 'description' => ts('Module or process which created this group.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_group.source', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'saved_search_id' => array( + ], + 'saved_search_id' => [ 'name' => 'saved_search_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Saved Search ID') , - 'description' => 'FK to saved search table.', + 'title' => ts('Saved Search ID'), + 'description' => ts('FK to saved search table.'), + 'where' => 'civicrm_group.saved_search_id', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_SavedSearch', - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Group Enabled') , - 'description' => 'Is this entry active?', + 'title' => ts('Group Enabled'), + 'description' => ts('Is this entry active?'), + 'where' => 'civicrm_group.is_active', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'visibility' => array( + ], + 'visibility' => [ 'name' => 'visibility', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Visibility Setting') , - 'description' => 'In what context(s) is this field visible.', + 'title' => ts('Group Visibility Setting'), + 'description' => ts('In what context(s) is this field visible.'), 'maxlength' => 24, 'size' => CRM_Utils_Type::MEDIUM, + 'where' => 'civicrm_group.visibility', 'default' => 'User and User Admin Only', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::groupVisibility', - ) - ) , - 'where_clause' => array( + ], + ], + 'where_clause' => [ 'name' => 'where_clause', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Group Where Clause') , - 'description' => 'the sql where clause if a saved search acl', + 'title' => ts('Group Where Clause'), + 'description' => ts('the sql where clause if a saved search acl'), + 'where' => 'civicrm_group.where_clause', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'select_tables' => array( + ], + 'select_tables' => [ 'name' => 'select_tables', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Tables For Select Clause') , - 'description' => 'the tables to be included in a select data', + 'title' => ts('Tables For Select Clause'), + 'description' => ts('the tables to be included in a select data'), + 'where' => 'civicrm_group.select_tables', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'where_tables' => array( + 'serialize' => self::SERIALIZE_PHP, + ], + 'where_tables' => [ 'name' => 'where_tables', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Tables For Where Clause') , - 'description' => 'the tables to be included in the count statement', + 'title' => ts('Tables For Where Clause'), + 'description' => ts('the tables to be included in the count statement'), + 'where' => 'civicrm_group.where_tables', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'group_type' => array( + 'serialize' => self::SERIALIZE_PHP, + ], + 'group_type' => [ 'name' => 'group_type', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Type') , - 'description' => 'FK to group type', + 'title' => ts('Group Type'), + 'description' => ts('FK to group type'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_group.group_type', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - 'pseudoconstant' => array( + 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND, + 'pseudoconstant' => [ 'optionGroupName' => 'group_type', 'optionEditPath' => 'civicrm/admin/options/group_type', - ) - ) , - 'cache_date' => array( + ], + ], + 'cache_date' => [ 'name' => 'cache_date', 'type' => CRM_Utils_Type::T_TIMESTAMP, - 'title' => ts('Group Cache Date') , - 'description' => 'Date when we created the cache for a smart group', - 'required' => false, + 'title' => ts('Group Cache Date'), + 'description' => ts('Date when we created the cache for a smart group'), + 'required' => FALSE, + 'where' => 'civicrm_group.cache_date', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'refresh_date' => array( + ], + 'refresh_date' => [ 'name' => 'refresh_date', 'type' => CRM_Utils_Type::T_TIMESTAMP, - 'title' => ts('Next Group Refresh Time') , - 'description' => 'Date and time when we need to refresh the cache next.', - 'required' => false, + 'title' => ts('Next Group Refresh Time'), + 'description' => ts('Date and time when we need to refresh the cache next.'), + 'required' => FALSE, + 'where' => 'civicrm_group.refresh_date', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'parents' => array( + ], + 'parents' => [ 'name' => 'parents', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Group Parents') , - 'description' => 'IDs of the parent(s)', + 'title' => ts('Group Parents'), + 'description' => ts('IDs of the parent(s)'), + 'where' => 'civicrm_group.parents', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'children' => array( + 'serialize' => self::SERIALIZE_COMMA, + 'pseudoconstant' => [ + 'callback' => 'CRM_Core_PseudoConstant::allGroup', + ], + ], + 'children' => [ 'name' => 'children', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Group Children') , - 'description' => 'IDs of the child(ren)', + 'title' => ts('Group Children'), + 'description' => ts('IDs of the child(ren)'), + 'where' => 'civicrm_group.children', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'is_hidden' => array( + ], + 'is_hidden' => [ 'name' => 'is_hidden', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Group is Hidden') , - 'description' => 'Is this group hidden?', + 'title' => ts('Group is Hidden'), + 'description' => ts('Is this group hidden?'), + 'where' => 'civicrm_group.is_hidden', + 'default' => '0', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'is_reserved' => array( + ], + 'is_reserved' => [ 'name' => 'is_reserved', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Group is Reserved') , + 'title' => ts('Group is Reserved'), + 'where' => 'civicrm_group.is_reserved', + 'default' => '0', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, - ) , - 'created_id' => array( + ], + 'created_id' => [ 'name' => 'created_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Created By') , - 'description' => 'FK to contact table.', + 'title' => ts('Group Created By'), + 'description' => ts('FK to contact table.'), + 'where' => 'civicrm_group.created_id', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'modified_id' => array( + ], + 'modified_id' => [ 'name' => 'modified_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Modified By') , - 'description' => 'FK to contact table.', + 'title' => ts('Group Modified By'), + 'description' => ts('FK to contact table.'), + 'where' => 'civicrm_group.modified_id', 'table_name' => 'civicrm_group', 'entity' => 'Group', 'bao' => 'CRM_Contact_BAO_Group', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -471,10 +506,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -482,42 +518,48 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_group_type' => array( + $indices = [ + 'index_group_type' => [ 'name' => 'index_group_type', - 'field' => array( + 'field' => [ 0 => 'group_type', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_group::0::group_type', - ) , - 'UI_title' => array( + ], + 'UI_title' => [ 'name' => 'UI_title', - 'field' => array( + 'field' => [ 0 => 'title', - ) , - 'localizable' => true, - 'unique' => true, + ], + 'localizable' => TRUE, + 'unique' => TRUE, 'sig' => 'civicrm_group::1::title', - ) , - 'UI_name' => array( + ], + 'UI_name' => [ 'name' => 'UI_name', - 'field' => array( + 'field' => [ 0 => 'name', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_group::1::name', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/GroupContact.php b/CRM/Contact/DAO/GroupContact.php index 380588a7d343..aa20c459b680 100644 --- a/CRM/Contact/DAO/GroupContact.php +++ b/CRM/Contact/DAO/GroupContact.php @@ -1,239 +1,235 @@ __table = 'civicrm_group_contact'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'group_id', 'civicrm_group', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'location_id', 'civicrm_loc_block', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'email_id', 'civicrm_email', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'location_id', 'civicrm_loc_block', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'email_id', 'civicrm_email', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Contact ID') , - 'description' => 'primary key', - 'required' => true, + 'title' => ts('Group Contact ID'), + 'description' => ts('primary key'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact.id', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, - ) , - 'group_id' => array( + ], + 'group_id' => [ 'name' => 'group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group ID') , - 'description' => 'FK to civicrm_group', - 'required' => true, + 'title' => ts('Group ID'), + 'description' => ts('FK to civicrm_group'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact.group_id', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_group', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'contact_id' => array( + ], + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to civicrm_contact', - 'required' => true, + 'title' => ts('Contact ID'), + 'description' => ts('FK to civicrm_contact'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact.contact_id', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'status' => array( + ], + 'status' => [ 'name' => 'status', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Contact Status') , - 'description' => 'status of contact relative to membership in group', + 'title' => ts('Group Contact Status'), + 'description' => ts('status of contact relative to membership in group'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_group_contact.status', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::groupContactStatus', - ) - ) , - 'location_id' => array( + ], + ], + 'location_id' => [ 'name' => 'location_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Contact Location') , - 'description' => 'Optional location to associate with this membership', + 'title' => ts('Group Contact Location'), + 'description' => ts('Optional location to associate with this membership'), + 'where' => 'civicrm_group_contact.location_id', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_LocBlock', - ) , - 'email_id' => array( + ], + 'email_id' => [ 'name' => 'email_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Contact Email') , - 'description' => 'Optional email to associate with this membership', + 'title' => ts('Group Contact Email'), + 'description' => ts('Optional email to associate with this membership'), + 'where' => 'civicrm_group_contact.email_id', 'table_name' => 'civicrm_group_contact', 'entity' => 'GroupContact', 'bao' => 'CRM_Contact_BAO_GroupContact', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_Email', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -241,10 +237,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_contact', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_contact', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -252,26 +249,32 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_contact', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_contact', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_contact_group' => array( + $indices = [ + 'UI_contact_group' => [ 'name' => 'UI_contact_group', - 'field' => array( + 'field' => [ 0 => 'contact_id', 1 => 'group_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_group_contact::1::contact_id::group_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/GroupContactCache.php b/CRM/Contact/DAO/GroupContactCache.php index d85be450bd62..8c8c09f29ede 100644 --- a/CRM/Contact/DAO/GroupContactCache.php +++ b/CRM/Contact/DAO/GroupContactCache.php @@ -1,179 +1,169 @@ __table = 'civicrm_group_contact_cache'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'group_id', 'civicrm_group', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Contact Cache ID') , - 'description' => 'primary key', - 'required' => true, + 'title' => ts('Group Contact Cache ID'), + 'description' => ts('primary key'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact_cache.id', 'table_name' => 'civicrm_group_contact_cache', 'entity' => 'GroupContactCache', 'bao' => 'CRM_Contact_BAO_GroupContactCache', 'localizable' => 0, - ) , - 'group_id' => array( + ], + 'group_id' => [ 'name' => 'group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group') , - 'description' => 'FK to civicrm_group', - 'required' => true, + 'title' => ts('Group'), + 'description' => ts('FK to civicrm_group'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact_cache.group_id', 'table_name' => 'civicrm_group_contact_cache', 'entity' => 'GroupContactCache', 'bao' => 'CRM_Contact_BAO_GroupContactCache', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_group', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'contact_id' => array( + ], + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to civicrm_contact', - 'required' => true, + 'title' => ts('Contact ID'), + 'description' => ts('FK to civicrm_contact'), + 'required' => TRUE, + 'where' => 'civicrm_group_contact_cache.contact_id', 'table_name' => 'civicrm_group_contact_cache', 'entity' => 'GroupContactCache', 'bao' => 'CRM_Contact_BAO_GroupContactCache', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -181,10 +171,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_contact_cache', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_contact_cache', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -192,26 +183,32 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_contact_cache', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_contact_cache', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_contact_group' => array( + $indices = [ + 'UI_contact_group' => [ 'name' => 'UI_contact_group', - 'field' => array( + 'field' => [ 0 => 'contact_id', 1 => 'group_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_group_contact_cache::1::contact_id::group_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/GroupNesting.php b/CRM/Contact/DAO/GroupNesting.php index 255e80ac9963..71cae5d79f0b 100644 --- a/CRM/Contact/DAO/GroupNesting.php +++ b/CRM/Contact/DAO/GroupNesting.php @@ -1,171 +1,161 @@ __table = 'civicrm_group_nesting'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'child_group_id', 'civicrm_group', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'parent_group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'child_group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'parent_group_id', 'civicrm_group', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Nesting ID') , - 'description' => 'Relationship ID', - 'required' => true, + 'title' => ts('Group Nesting ID'), + 'description' => ts('Relationship ID'), + 'required' => TRUE, + 'where' => 'civicrm_group_nesting.id', 'table_name' => 'civicrm_group_nesting', 'entity' => 'GroupNesting', 'bao' => 'CRM_Contact_BAO_GroupNesting', 'localizable' => 0, - ) , - 'child_group_id' => array( + ], + 'child_group_id' => [ 'name' => 'child_group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Child Group') , - 'description' => 'ID of the child group', - 'required' => true, + 'title' => ts('Child Group'), + 'description' => ts('ID of the child group'), + 'required' => TRUE, + 'where' => 'civicrm_group_nesting.child_group_id', 'table_name' => 'civicrm_group_nesting', 'entity' => 'GroupNesting', 'bao' => 'CRM_Contact_BAO_GroupNesting', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - ) , - 'parent_group_id' => array( + ], + 'parent_group_id' => [ 'name' => 'parent_group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Parent Group') , - 'description' => 'ID of the parent group', - 'required' => true, + 'title' => ts('Parent Group'), + 'description' => ts('ID of the parent group'), + 'required' => TRUE, + 'where' => 'civicrm_group_nesting.parent_group_id', 'table_name' => 'civicrm_group_nesting', 'entity' => 'GroupNesting', 'bao' => 'CRM_Contact_BAO_GroupNesting', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -173,10 +163,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_nesting', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_nesting', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -184,15 +175,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_nesting', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_nesting', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/GroupOrganization.php b/CRM/Contact/DAO/GroupOrganization.php index 718a2bfda29c..a6491b4f9f34 100644 --- a/CRM/Contact/DAO/GroupOrganization.php +++ b/CRM/Contact/DAO/GroupOrganization.php @@ -1,179 +1,169 @@ __table = 'civicrm_group_organization'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'group_id', 'civicrm_group', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'organization_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'organization_id', 'civicrm_contact', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Organization ID') , - 'description' => 'Relationship ID', - 'required' => true, + 'title' => ts('Group Organization ID'), + 'description' => ts('Relationship ID'), + 'required' => TRUE, + 'where' => 'civicrm_group_organization.id', 'table_name' => 'civicrm_group_organization', 'entity' => 'GroupOrganization', 'bao' => 'CRM_Contact_BAO_GroupOrganization', 'localizable' => 0, - ) , - 'group_id' => array( + ], + 'group_id' => [ 'name' => 'group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group') , - 'description' => 'ID of the group', - 'required' => true, + 'title' => ts('Group'), + 'description' => ts('ID of the group'), + 'required' => TRUE, + 'where' => 'civicrm_group_organization.group_id', 'table_name' => 'civicrm_group_organization', 'entity' => 'GroupOrganization', 'bao' => 'CRM_Contact_BAO_GroupOrganization', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_group', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'organization_id' => array( + ], + ], + 'organization_id' => [ 'name' => 'organization_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Organization') , - 'description' => 'ID of the Organization Contact', - 'required' => true, + 'title' => ts('Organization'), + 'description' => ts('ID of the Organization Contact'), + 'required' => TRUE, + 'where' => 'civicrm_group_organization.organization_id', 'table_name' => 'civicrm_group_organization', 'entity' => 'GroupOrganization', 'bao' => 'CRM_Contact_BAO_GroupOrganization', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -181,10 +171,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_organization', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'group_organization', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -192,26 +183,32 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_organization', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'group_organization', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_group_organization' => array( + $indices = [ + 'UI_group_organization' => [ 'name' => 'UI_group_organization', - 'field' => array( + 'field' => [ 0 => 'group_id', 1 => 'organization_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_group_organization::1::group_id::organization_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/Relationship.php b/CRM/Contact/DAO/Relationship.php index 5953567a02b1..2849aead67fa 100644 --- a/CRM/Contact/DAO/Relationship.php +++ b/CRM/Contact/DAO/Relationship.php @@ -1,338 +1,350 @@ __table = 'civicrm_relationship'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id_a', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id_b', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'relationship_type_id', 'civicrm_relationship_type', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'case_id', 'civicrm_case', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id_a', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id_b', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'relationship_type_id', 'civicrm_relationship_type', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'case_id', 'civicrm_case', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Relationship ID') , - 'description' => 'Relationship ID', - 'required' => true, + 'title' => ts('Relationship ID'), + 'description' => ts('Relationship ID'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.id', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - ) , - 'contact_id_a' => array( + ], + 'contact_id_a' => [ 'name' => 'contact_id_a', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact A') , - 'description' => 'id of the first contact', - 'required' => true, + 'title' => ts('Contact A'), + 'description' => ts('id of the first contact'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.contact_id_a', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'contact_id_b' => array( + ], + 'contact_id_b' => [ 'name' => 'contact_id_b', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact B') , - 'description' => 'id of the second contact', - 'required' => true, + 'title' => ts('Contact B'), + 'description' => ts('id of the second contact'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.contact_id_b', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => array( + 'html' => [ 'type' => 'EntityRef', - ) , - ) , - 'relationship_type_id' => array( + ], + ], + 'relationship_type_id' => [ 'name' => 'relationship_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Relationship Type') , - 'description' => 'id of the relationship', - 'required' => true, + 'title' => ts('Relationship Type'), + 'description' => ts('id of the relationship'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.relationship_type_id', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_RelationshipType', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - ) , - 'start_date' => array( + ], + ], + 'start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Relationship Start Date') , - 'description' => 'date when the relationship started', + 'title' => ts('Relationship Start Date'), + 'description' => ts('date when the relationship started'), + 'where' => 'civicrm_relationship.start_date', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'end_date' => array( + 'formatType' => 'activityDate', + ], + ], + 'end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Relationship End Date') , - 'description' => 'date when the relationship ended', + 'title' => ts('Relationship End Date'), + 'description' => ts('date when the relationship ended'), + 'where' => 'civicrm_relationship.end_date', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'is_active' => array( + 'formatType' => 'activityDate', + ], + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Relationship Is Active') , - 'description' => 'is the relationship active ?', + 'title' => ts('Relationship Is Active'), + 'description' => ts('is the relationship active ?'), + 'where' => 'civicrm_relationship.is_active', 'default' => '1', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'description' => array( + ], + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Description') , - 'description' => 'Optional verbose description for the relationship.', + 'title' => ts('Relationship Description'), + 'description' => ts('Optional verbose description for the relationship.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_relationship.description', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'is_permission_a_b' => array( + ], + ], + 'is_permission_a_b' => [ 'name' => 'is_permission_a_b', - 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Contact A has Permission Over Contact B') , - 'description' => 'is contact a has permission to view / edit contact and - related data for contact b ? - ', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Contact A has Permission Over Contact B'), + 'description' => ts('Permission that Contact A has to view/update Contact B'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.is_permission_a_b', + 'default' => '0', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( - 'type' => 'CheckBox', - ) , - ) , - 'is_permission_b_a' => array( + 'html' => [ + 'type' => 'Radio', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Core_SelectValues::getPermissionedRelationshipOptions', + ], + ], + 'is_permission_b_a' => [ 'name' => 'is_permission_b_a', - 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Contact B has Permission Over Contact A') , - 'description' => 'is contact b has permission to view / edit contact and - related data for contact a ? - ', + 'type' => CRM_Utils_Type::T_INT, + 'title' => ts('Contact B has Permission Over Contact A'), + 'description' => ts('Permission that Contact B has to view/update Contact A'), + 'required' => TRUE, + 'where' => 'civicrm_relationship.is_permission_b_a', + 'default' => '0', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, - 'html' => array( - 'type' => 'CheckBox', - ) , - ) , - 'case_id' => array( + 'html' => [ + 'type' => 'Radio', + ], + 'pseudoconstant' => [ + 'callback' => 'CRM_Core_SelectValues::getPermissionedRelationshipOptions', + ], + ], + 'case_id' => [ 'name' => 'case_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Relationship Case') , - 'description' => 'FK to civicrm_case', + 'title' => ts('Relationship Case'), + 'description' => ts('FK to civicrm_case'), + 'where' => 'civicrm_relationship.case_id', 'default' => 'NULL', 'table_name' => 'civicrm_relationship', 'entity' => 'Relationship', 'bao' => 'CRM_Contact_BAO_Relationship', 'localizable' => 0, 'FKClassName' => 'CRM_Case_DAO_Case', - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -340,10 +352,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'relationship', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'relationship', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -351,15 +364,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'relationship', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'relationship', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/RelationshipType.php b/CRM/Contact/DAO/RelationshipType.php index 16e0125fe122..a4509a7b8bbb 100644 --- a/CRM/Contact/DAO/RelationshipType.php +++ b/CRM/Contact/DAO/RelationshipType.php @@ -1,354 +1,373 @@ __table = 'civicrm_relationship_type'; parent::__construct(); } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Relationship Type ID') , - 'description' => 'Primary key', - 'required' => true, + 'title' => ts('Relationship Type ID'), + 'description' => ts('Primary key'), + 'required' => TRUE, + 'where' => 'civicrm_relationship_type.id', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - ) , - 'name_a_b' => array( + ], + 'name_a_b' => [ 'name' => 'name_a_b', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Type Name A to B') , - 'description' => 'name for relationship of contact_a to contact_b.', + 'title' => ts('Relationship Type Name A to B'), + 'description' => ts('name for relationship of contact_a to contact_b.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.name_a_b', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - ) , - 'label_a_b' => array( + ], + 'label_a_b' => [ 'name' => 'label_a_b', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Type Label A to B') , - 'description' => 'label for relationship of contact_a to contact_b.', + 'title' => ts('Relationship Type Label A to B'), + 'description' => ts('label for relationship of contact_a to contact_b.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.label_a_b', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 1, - ) , - 'name_b_a' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'name_b_a' => [ 'name' => 'name_b_a', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Type Name B to A') , - 'description' => 'Optional name for relationship of contact_b to contact_a.', + 'title' => ts('Relationship Type Name B to A'), + 'description' => ts('Optional name for relationship of contact_b to contact_a.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.name_b_a', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - ) , - 'label_b_a' => array( + ], + 'label_b_a' => [ 'name' => 'label_b_a', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Type Label B to A') , - 'description' => 'Optional label for relationship of contact_b to contact_a.', + 'title' => ts('Relationship Type Label B to A'), + 'description' => ts('Optional label for relationship of contact_b to contact_a.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.label_b_a', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 1, - ) , - 'description' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Relationship Description') , - 'description' => 'Optional verbose description of the relationship type.', + 'title' => ts('Relationship Description'), + 'description' => ts('Optional verbose description of the relationship type.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_relationship_type.description', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 1, - ) , - 'contact_type_a' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'contact_type_a' => [ 'name' => 'contact_type_a', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type for Contact A') , - 'description' => 'If defined, contact_a in a relationship of this type must be a specific contact_type.', + 'title' => ts('Contact Type for Contact A'), + 'description' => ts('If defined, contact_a in a relationship of this type must be a specific contact_type.'), 'maxlength' => 12, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_relationship_type.contact_type_a', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NULL', - ) - ) , - 'contact_type_b' => array( + ], + ], + 'contact_type_b' => [ 'name' => 'contact_type_b', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Type for Contact B') , - 'description' => 'If defined, contact_b in a relationship of this type must be a specific contact_type.', + 'title' => ts('Contact Type for Contact B'), + 'description' => ts('If defined, contact_b in a relationship of this type must be a specific contact_type.'), 'maxlength' => 12, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_relationship_type.contact_type_b', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NULL', - ) - ) , - 'contact_sub_type_a' => array( + ], + ], + 'contact_sub_type_a' => [ 'name' => 'contact_sub_type_a', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Subtype A') , - 'description' => 'If defined, contact_sub_type_a in a relationship of this type must be a specific contact_sub_type. - ', + 'title' => ts('Contact Subtype A'), + 'description' => ts('If defined, contact_sub_type_a in a relationship of this type must be a specific contact_sub_type.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.contact_sub_type_a', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NOT NULL', - ) - ) , - 'contact_sub_type_b' => array( + ], + ], + 'contact_sub_type_b' => [ 'name' => 'contact_sub_type_b', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contact Subtype B') , - 'description' => 'If defined, contact_sub_type_b in a relationship of this type must be a specific contact_sub_type. - ', + 'title' => ts('Contact Subtype B'), + 'description' => ts('If defined, contact_sub_type_b in a relationship of this type must be a specific contact_sub_type.'), 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_relationship_type.contact_sub_type_b', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contact_type', 'keyColumn' => 'name', 'labelColumn' => 'label', 'condition' => 'parent_id IS NOT NULL', - ) - ) , - 'is_reserved' => array( + ], + ], + 'is_reserved' => [ 'name' => 'is_reserved', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Relationship Type is Reserved') , - 'description' => 'Is this relationship type a predefined system type (can not be changed or de-activated)?', + 'title' => ts('Relationship Type is Reserved'), + 'description' => ts('Is this relationship type a predefined system type (can not be changed or de-activated)?'), + 'where' => 'civicrm_relationship_type.is_reserved', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - ) , - 'is_active' => array( + 'html' => [ + 'type' => 'CheckBox', + ], + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Relationship Type is Active') , - 'description' => 'Is this relationship type currently active (i.e. can be used when creating or editing relationships)? - ', + 'title' => ts('Relationship Type is Active'), + 'description' => ts('Is this relationship type currently active (i.e. can be used when creating or editing relationships)?'), + 'where' => 'civicrm_relationship_type.is_active', 'default' => '1', 'table_name' => 'civicrm_relationship_type', 'entity' => 'RelationshipType', 'bao' => 'CRM_Contact_BAO_RelationshipType', 'localizable' => 0, - ) , - ); + 'html' => [ + 'type' => 'CheckBox', + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -356,10 +375,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'relationship_type', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'relationship_type', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -367,34 +387,40 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'relationship_type', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'relationship_type', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_name_a_b' => array( + $indices = [ + 'UI_name_a_b' => [ 'name' => 'UI_name_a_b', - 'field' => array( + 'field' => [ 0 => 'name_a_b', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_relationship_type::1::name_a_b', - ) , - 'UI_name_b_a' => array( + ], + 'UI_name_b_a' => [ 'name' => 'UI_name_b_a', - 'field' => array( + 'field' => [ 0 => 'name_b_a', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_relationship_type::1::name_b_a', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/SavedSearch.php b/CRM/Contact/DAO/SavedSearch.php index f6d87c8da145..cd29f674953b 100644 --- a/CRM/Contact/DAO/SavedSearch.php +++ b/CRM/Contact/DAO/SavedSearch.php @@ -1,236 +1,234 @@ __table = 'civicrm_saved_search'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'mapping_id', 'civicrm_mapping', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'mapping_id', 'civicrm_mapping', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Saved Search ID') , - 'description' => 'Saved Search ID', - 'required' => true, + 'title' => ts('Saved Search ID'), + 'description' => ts('Saved Search ID'), + 'required' => TRUE, + 'where' => 'civicrm_saved_search.id', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - 'form_values' => array( + ], + 'form_values' => [ 'name' => 'form_values', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Submitted Form Values') , - 'description' => 'Submitted form values for this search', - 'import' => true, + 'title' => ts('Submitted Form Values'), + 'description' => ts('Submitted form values for this search'), + 'import' => TRUE, 'where' => 'civicrm_saved_search.form_values', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - 'mapping_id' => array( + 'serialize' => self::SERIALIZE_PHP, + ], + 'mapping_id' => [ 'name' => 'mapping_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Mapping ID') , - 'description' => 'Foreign key to civicrm_mapping used for saved search-builder searches.', + 'title' => ts('Mapping ID'), + 'description' => ts('Foreign key to civicrm_mapping used for saved search-builder searches.'), + 'where' => 'civicrm_saved_search.mapping_id', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_Mapping', - ) , - 'search_custom_id' => array( + ], + 'search_custom_id' => [ 'name' => 'search_custom_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Option Value ID') , - 'description' => 'Foreign key to civicrm_option value table used for saved custom searches.', + 'title' => ts('Option Value ID'), + 'description' => ts('Foreign key to civicrm_option value table used for saved custom searches.'), + 'where' => 'civicrm_saved_search.search_custom_id', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - 'where_clause' => array( + ], + 'where_clause' => [ 'name' => 'where_clause', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Where Clause') , - 'description' => 'the sql where clause if a saved search acl', + 'title' => ts('Where Clause'), + 'description' => ts('the sql where clause if a saved search acl'), + 'where' => 'civicrm_saved_search.where_clause', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - 'select_tables' => array( + ], + 'select_tables' => [ 'name' => 'select_tables', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Select Tables') , - 'description' => 'the tables to be included in a select data', + 'title' => ts('Select Tables'), + 'description' => ts('the tables to be included in a select data'), + 'where' => 'civicrm_saved_search.select_tables', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - 'where_tables' => array( + 'serialize' => self::SERIALIZE_PHP, + ], + 'where_tables' => [ 'name' => 'where_tables', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Where Tables') , - 'description' => 'the tables to be included in the count statement', + 'title' => ts('Where Tables'), + 'description' => ts('the tables to be included in the count statement'), + 'where' => 'civicrm_saved_search.where_tables', 'table_name' => 'civicrm_saved_search', 'entity' => 'SavedSearch', 'bao' => 'CRM_Contact_BAO_SavedSearch', 'localizable' => 0, - ) , - ); + 'serialize' => self::SERIALIZE_PHP, + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -238,10 +236,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'saved_search', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'saved_search', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -249,15 +248,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'saved_search', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'saved_search', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/DAO/SubscriptionHistory.php b/CRM/Contact/DAO/SubscriptionHistory.php index a29c982fb9c2..805e02ca3a38 100644 --- a/CRM/Contact/DAO/SubscriptionHistory.php +++ b/CRM/Contact/DAO/SubscriptionHistory.php @@ -1,258 +1,257 @@ __table = 'civicrm_subscription_history'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'group_id', 'civicrm_group', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'group_id', 'civicrm_group', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group Membership History ID') , - 'description' => 'Internal Id', - 'required' => true, + 'title' => ts('Group Membership History ID'), + 'description' => ts('Internal Id'), + 'required' => TRUE, + 'where' => 'civicrm_subscription_history.id', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'Contact Id', - 'required' => true, + 'title' => ts('Contact ID'), + 'description' => ts('Contact Id'), + 'required' => TRUE, + 'where' => 'civicrm_subscription_history.contact_id', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'group_id' => array( + ], + 'group_id' => [ 'name' => 'group_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Group') , - 'description' => 'Group Id', + 'title' => ts('Group'), + 'description' => ts('Group Id'), + 'where' => 'civicrm_subscription_history.group_id', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Group', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_group', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'date' => array( + ], + ], + 'date' => [ 'name' => 'date', - 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Group Membership Action Date') , - 'description' => 'Date of the (un)subscription', - 'required' => true, + 'type' => CRM_Utils_Type::T_TIMESTAMP, + 'title' => ts('Group Membership Action Date'), + 'description' => ts('Date of the (un)subscription'), + 'required' => TRUE, + 'where' => 'civicrm_subscription_history.date', + 'default' => 'CURRENT_TIMESTAMP', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, - ) , - 'method' => array( + ], + 'method' => [ 'name' => 'method', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Membership Action') , - 'description' => 'How the (un)subscription was triggered', + 'title' => ts('Group Membership Action'), + 'description' => ts('How the (un)subscription was triggered'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_subscription_history.method', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::getSubscriptionHistoryMethods', - ) - ) , - 'status' => array( + ], + ], + 'status' => [ 'name' => 'status', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Membership Status') , - 'description' => 'The state of the contact within the group', + 'title' => ts('Group Membership Status'), + 'description' => ts('The state of the contact within the group'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_subscription_history.status', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::groupContactStatus', - ) - ) , - 'tracking' => array( + ], + ], + 'tracking' => [ 'name' => 'tracking', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Group Membership Tracking') , - 'description' => 'IP address or other tracking info', + 'title' => ts('Group Membership Tracking'), + 'description' => ts('IP address or other tracking info'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_subscription_history.tracking', 'table_name' => 'civicrm_subscription_history', 'entity' => 'SubscriptionHistory', 'bao' => 'CRM_Contact_BAO_SubscriptionHistory', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -260,10 +259,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'subscription_history', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'subscription_history', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -271,15 +271,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'subscription_history', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'subscription_history', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contact/Form/Contact.php b/CRM/Contact/Form/Contact.php index 0a1c077b62c1..11bef4a62927 100644 --- a/CRM/Contact/Form/Contact.php +++ b/CRM/Contact/Form/Contact.php @@ -1,9 +1,9 @@ _contactType, - array('Individual', 'Household', 'Organization') + ['Individual', 'Household', 'Organization'] ) ) { CRM_Core_Error::statusBounce(ts('Could not get a contact id and/or contact type')); @@ -177,7 +180,7 @@ public function preProcess() { $this->_contactSubType && !(CRM_Contact_BAO_ContactType::isExtendsContactType($this->_contactSubType, $this->_contactType, TRUE)) ) { - CRM_Core_Error::statusBounce(ts("Could not get a valid contact subtype for contact type '%1'", array(1 => $this->_contactType))); + CRM_Core_Error::statusBounce(ts("Could not get a valid contact subtype for contact type '%1'", [1 => $this->_contactType])); } $this->_gid = CRM_Utils_Request::retrieve('gid', 'Integer', @@ -193,7 +196,7 @@ public function preProcess() { ); $typeLabel = implode(' / ', $typeLabel); - CRM_Utils_System::setTitle(ts('New %1', array(1 => $typeLabel))); + CRM_Utils_System::setTitle(ts('New %1', [1 => $typeLabel])); $session->pushUserContext(CRM_Utils_System::url('civicrm/dashboard', 'reset=1')); $this->_contactId = NULL; } @@ -204,13 +207,13 @@ public function preProcess() { } if ($this->_contactId) { - $defaults = array(); - $params = array('id' => $this->_contactId); - $returnProperities = array('id', 'contact_type', 'contact_sub_type', 'modified_date', 'is_deceased'); + $defaults = []; + $params = ['id' => $this->_contactId]; + $returnProperities = ['id', 'contact_type', 'contact_sub_type', 'modified_date', 'is_deceased']; CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Contact', $params, $defaults, $returnProperities); if (empty($defaults['id'])) { - CRM_Core_Error::statusBounce(ts('A Contact with that ID does not exist: %1', array(1 => $this->_contactId))); + CRM_Core_Error::statusBounce(ts('A Contact with that ID does not exist: %1', [1 => $this->_contactId])); } $this->_contactType = CRM_Utils_Array::value('contact_type', $defaults); @@ -224,9 +227,9 @@ public function preProcess() { $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); if ($defaults['is_deceased']) { - $displayName .= ' (deceased)'; + $displayName .= ' (' . ts('deceased') . ')'; } - $displayName = ts('Edit %1', array(1 => $displayName)); + $displayName = ts('Edit %1', [1 => $displayName]); // Check if this is default domain contact CRM-10482 if (CRM_Contact_BAO_Contact::checkDomainContact($this->_contactId)) { @@ -235,7 +238,7 @@ public function preProcess() { // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container CRM_Utils_System::setTitle($displayName); - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this); $urlParams = 'reset=1&cid=' . $this->_contactId; @@ -256,13 +259,13 @@ public function preProcess() { $this->_values = $values; } else { - $params = array( + $params = [ 'id' => $this->_contactId, 'contact_id' => $this->_contactId, 'noRelationships' => TRUE, 'noNotes' => TRUE, 'noGroups' => TRUE, - ); + ]; $contact = CRM_Contact_BAO_Contact::retrieve($params, $this->_values, TRUE); $this->set('values', $this->_values); @@ -430,7 +433,7 @@ public function setDefaultValues() { } // set defaults for blocks ( custom data, address, communication preference, notes, tags and groups ) foreach ($this->_editOptions as $name => $label) { - if (!in_array($name, array('Address', 'Notes'))) { + if (!in_array($name, ['Address', 'Notes'])) { $className = 'CRM_Contact_Form_Edit_' . $name; $className::setDefaultValues($this, $defaults); } @@ -561,19 +564,19 @@ public function addRules() { return; } - $this->addFormRule(array('CRM_Contact_Form_Edit_' . $this->_contactType, 'formRule'), $this->_contactId); + $this->addFormRule(['CRM_Contact_Form_Edit_' . $this->_contactType, 'formRule'], $this->_contactId); // Call Locking check if editing existing contact if ($this->_contactId) { - $this->addFormRule(array('CRM_Contact_Form_Edit_Lock', 'formRule'), $this->_contactId); + $this->addFormRule(['CRM_Contact_Form_Edit_Lock', 'formRule'], $this->_contactId); } if (array_key_exists('Address', $this->_editOptions)) { - $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Edit_Address', 'formRule'], $this); } if (array_key_exists('CommunicationPreferences', $this->_editOptions)) { - $this->addFormRule(array('CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'], $this); } } @@ -586,11 +589,12 @@ public function addRules() { * List of errors to be posted back to the form. * @param int $contactId * Contact id if doing update. + * @param string $contactType * * @return bool * email/openId */ - public static function formRule($fields, &$errors, $contactId = NULL) { + public static function formRule($fields, &$errors, $contactId, $contactType) { $config = CRM_Core_Config::singleton(); // validations. @@ -615,10 +619,11 @@ public static function formRule($fields, &$errors, $contactId = NULL) { $blocks['Address'] = $otherEditOptions['Address']; } - $openIds = array(); + $website_types = []; + $openIds = []; $primaryID = FALSE; foreach ($blocks as $name => $label) { - $hasData = $hasPrimary = array(); + $hasData = $hasPrimary = []; $name = strtolower($name); if (!empty($fields[$name]) && is_array($fields[$name])) { foreach ($fields[$name] as $instance => $blockValues) { @@ -629,8 +634,17 @@ public static function formRule($fields, &$errors, $contactId = NULL) { } if ($dataExists) { - // skip remaining checks for website if ($name == 'website') { + if (!empty($blockValues['website_type_id'])) { + if (empty($website_types[$blockValues['website_type_id']])) { + $website_types[$blockValues['website_type_id']] = $blockValues['website_type_id']; + } + else { + $errors["{$name}[1][website_type_id]"] = ts('Contacts may only have one website of each type at most.'); + } + } + + // skip remaining checks for website continue; } @@ -638,17 +652,17 @@ public static function formRule($fields, &$errors, $contactId = NULL) { if (!empty($blockValues['is_primary'])) { $hasPrimary[] = $instance; if (!$primaryID && - in_array($name, array( + in_array($name, [ 'email', 'openid', - )) && !empty($blockValues[$name]) + ]) && !empty($blockValues[$name]) ) { $primaryID = $blockValues[$name]; } } if (empty($blockValues['location_type_id'])) { - $errors["{$name}[$instance][location_type_id]"] = ts('The Location Type should be set if there is %1 information.', array(1 => $label)); + $errors["{$name}[$instance][location_type_id]"] = ts('The Location Type should be set if there is %1 information.', [1 => $label]); } } @@ -657,18 +671,18 @@ public static function formRule($fields, &$errors, $contactId = NULL) { $oid->openid = $openIds[$instance] = CRM_Utils_Array::value($name, $blockValues); $cid = isset($contactId) ? $contactId : 0; if ($oid->find(TRUE) && ($oid->contact_id != $cid)) { - $errors["{$name}[$instance][openid]"] = ts('%1 already exist.', array(1 => $blocks['OpenID'])); + $errors["{$name}[$instance][openid]"] = ts('%1 already exist.', [1 => $blocks['OpenID']]); } } } if (empty($hasPrimary) && !empty($hasData)) { - $errors["{$name}[1][is_primary]"] = ts('One %1 should be marked as primary.', array(1 => $label)); + $errors["{$name}[1][is_primary]"] = ts('One %1 should be marked as primary.', [1 => $label]); } if (count($hasPrimary) > 1) { $errors["{$name}[" . array_pop($hasPrimary) . "][is_primary]"] = ts('Only one %1 can be marked as primary.', - array(1 => $label) + [1 => $label] ); } } @@ -678,7 +692,7 @@ public static function formRule($fields, &$errors, $contactId = NULL) { if (!empty($openIds) && (count(array_unique($openIds)) != count($openIds))) { foreach ($openIds as $instance => $value) { if (!array_key_exists($instance, array_unique($openIds))) { - $errors["openid[$instance][openid]"] = ts('%1 already used.', array(1 => $blocks['OpenID'])); + $errors["openid[$instance][openid]"] = ts('%1 already used.', [1 => $blocks['OpenID']]); } } } @@ -693,7 +707,7 @@ public static function formRule($fields, &$errors, $contactId = NULL) { if (isset($fields['address']) && is_array($fields['address']) ) { - $invalidStreetNumbers = array(); + $invalidStreetNumbers = []; foreach ($fields['address'] as $cnt => $address) { if ($streetNumber = CRM_Utils_Array::value('street_number', $address)) { $parsedAddress = CRM_Core_BAO_Address::parseStreetAddress($address['street_number']); @@ -708,11 +722,16 @@ public static function formRule($fields, &$errors, $contactId = NULL) { foreach ($invalidStreetNumbers as & $num) { $num = CRM_Contact_Form_Contact::ordinalNumber($num); } - $errors["address[$first][street_number]"] = ts('The street number you entered for the %1 address block(s) is not in an expected format. Street numbers may include numeric digit(s) followed by other characters. You can still enter the complete street address (unparsed) by clicking "Edit Complete Street Address".', array(1 => implode(', ', $invalidStreetNumbers))); + $errors["address[$first][street_number]"] = ts('The street number you entered for the %1 address block(s) is not in an expected format. Street numbers may include numeric digit(s) followed by other characters. You can still enter the complete street address (unparsed) by clicking "Edit Complete Street Address".', [1 => implode(', ', $invalidStreetNumbers)]); } } } + // Check for duplicate contact if it wasn't already handled by ajax or disabled + if (!Civi::settings()->get('contact_ajax_check_similar') || !empty($fields['_qf_Contact_refresh_dedupe'])) { + self::checkDuplicateContacts($fields, $errors, $contactId, $contactType); + } + return $primaryID; } @@ -728,19 +747,19 @@ public function buildQuickForm() { if ($this->_action == CRM_Core_Action::UPDATE) { $deleteExtra = json_encode(ts('Are you sure you want to delete contact image.')); - $deleteURL = array( - CRM_Core_Action::DELETE => array( + $deleteURL = [ + CRM_Core_Action::DELETE => [ 'name' => ts('Delete Contact Image'), 'url' => 'civicrm/contact/image', 'qs' => 'reset=1&cid=%%id%%&action=delete', 'extra' => 'onclick = "' . htmlspecialchars("if (confirm($deleteExtra)) this.href+='&confirmed=1'; else return false;") . '"', - ), - ); + ], + ]; $deleteURL = CRM_Core_Action::formLink($deleteURL, CRM_Core_Action::DELETE, - array( + [ 'id' => $this->_contactId, - ), + ], ts('more'), FALSE, 'contact.image.delete', @@ -754,6 +773,14 @@ public function buildQuickForm() { $className = 'CRM_Contact_Form_Edit_' . $this->_contactType; $className::buildQuickForm($this); + // Ajax duplicate checking + $checkSimilar = Civi::settings()->get('contact_ajax_check_similar'); + $this->assign('checkSimilar', $checkSimilar); + if ($checkSimilar == 1) { + $ruleParams = ['used' => 'Supervised', 'contact_type' => $this->_contactType]; + $this->assign('ruleFields', CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams)); + } + // build Custom data if Custom data present in edit option $buildCustomData = 'noCustomDataPresent'; if (array_key_exists('CustomData', $this->_editOptions)) { @@ -761,15 +788,15 @@ public function buildQuickForm() { } // subtype is a common field. lets keep it here - $subtypes = CRM_Contact_BAO_Contact::buildOptions('contact_sub_type', 'create', array('contact_type' => $this->_contactType)); + $subtypes = CRM_Contact_BAO_Contact::buildOptions('contact_sub_type', 'create', ['contact_type' => $this->_contactType]); if (!empty($subtypes)) { - $this->addField('contact_sub_type', array( + $this->addField('contact_sub_type', [ 'label' => ts('Contact Type'), 'options' => $subtypes, 'class' => $buildCustomData, 'multiple' => 'multiple', 'option_url' => NULL, - )); + ]); } // build edit blocks ( custom data, demographics, communication preference, notes, tags and groups ) @@ -794,7 +821,7 @@ public function buildQuickForm() { CRM_Contact_Form_Location::buildQuickForm($this); // add attachment - $this->addField('image_URL', array('maxlength' => '255', 'label' => ts('Browse/Upload Image'))); + $this->addField('image_URL', ['maxlength' => '255', 'label' => ts('Browse/Upload Image')]); // add the dedupe button $this->addElement('submit', @@ -810,26 +837,26 @@ public function buildQuickForm() { ts('Save With Duplicate Household') ); - $buttons = array( - array( + $buttons = [ + [ 'type' => 'upload', 'name' => ts('Save'), 'subName' => 'view', 'isDefault' => TRUE, - ), - ); + ], + ]; if (CRM_Core_Permission::check('add contacts')) { - $buttons[] = array( + $buttons[] = [ 'type' => 'upload', 'name' => ts('Save and New'), 'spacing' => '                 ', 'subName' => 'new', - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'cancel', 'name' => ts('Cancel'), - ); + ]; if (!empty($this->_values['contact_sub_type'])) { $this->_oldSubtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, @@ -853,6 +880,10 @@ public function postProcess() { //get the submitted values in an array $params = $this->controller->exportValues($this->_name); + if (!isset($params['preferred_communication_method'])) { + // If this field is empty QF will trim it so we have to add it in. + $params['preferred_communication_method'] = 'null'; + } $group = CRM_Utils_Array::value('group', $params); if (!empty($group) && is_array($group)) { @@ -862,8 +893,6 @@ public function postProcess() { } } - CRM_Contact_BAO_Contact_Optimizer::edit($params, $this->_preEditValues); - if (!empty($params['image_URL'])) { CRM_Contact_BAO_Contact::processImageParams($params); } @@ -881,7 +910,7 @@ public function postProcess() { $params['contact_type'] = $this->_contactType; if (empty($params['contact_sub_type']) && $this->_isContactSubType) { - $params['contact_sub_type'] = array($this->_contactSubType); + $params['contact_sub_type'] = [$this->_contactSubType]; } if ($this->_contactId) { @@ -896,11 +925,11 @@ public function postProcess() { if (isset($params['contact_id'])) { // process membership status for deceased contact - $deceasedParams = array( + $deceasedParams = [ 'contact_id' => CRM_Utils_Array::value('contact_id', $params), 'is_deceased' => CRM_Utils_Array::value('is_deceased', $params, FALSE), 'deceased_date' => CRM_Utils_Array::value('deceased_date', $params, NULL), - ); + ]; $updateMembershipMsg = $this->updateMembershipStatus($deceasedParams); } @@ -912,8 +941,6 @@ public function postProcess() { CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params); } - $customFields = CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, TRUE); - //CRM-5143 //if subtype is set, send subtype as extend to validate subtype customfield $customFieldExtends = (CRM_Utils_Array::value('contact_sub_type', $params)) ? $params['contact_sub_type'] : $params['contact_type']; @@ -939,11 +966,10 @@ public function postProcess() { // process shared contact address. CRM_Contact_BAO_Contact_Utils::processSharedAddress($params['address']); - if (!array_key_exists('TagsAndGroups', $this->_editOptions) && !empty($params['group'])) { + if (!array_key_exists('TagsAndGroups', $this->_editOptions)) { unset($params['group']); } - - if (!empty($params['contact_id']) && ($this->_action & CRM_Core_Action::UPDATE) && !empty($params['group'])) { + elseif (!empty($params['contact_id']) && ($this->_action & CRM_Core_Action::UPDATE)) { // figure out which all groups are intended to be removed $contactGroupList = CRM_Contact_BAO_GroupContact::getContactGroup($params['contact_id'], 'Added'); if (is_array($contactGroupList)) { @@ -962,7 +988,7 @@ public function postProcess() { $parseStatusMsg = self::parseAddressStatusMsg($parseResult); } - $blocks = array('email', 'phone', 'im', 'openid', 'address', 'website'); + $blocks = ['email', 'phone', 'im', 'openid', 'address', 'website']; foreach ($blocks as $block) { if (!empty($this->_preEditValues[$block]) && is_array($this->_preEditValues[$block])) { foreach ($this->_preEditValues[$block] as $count => $value) { @@ -981,10 +1007,10 @@ public function postProcess() { // status message if ($this->_contactId) { - $message = ts('%1 has been updated.', array(1 => $contact->display_name)); + $message = ts('%1 has been updated.', [1 => $contact->display_name]); } else { - $message = ts('%1 has been created.', array(1 => $contact->display_name)); + $message = ts('%1 has been created.', [1 => $contact->display_name]); } // set the contact ID @@ -992,8 +1018,10 @@ public function postProcess() { if (array_key_exists('TagsAndGroups', $this->_editOptions)) { //add contact to tags - CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $params['contact_id']); - + if (isset($params['tag'])) { + $params['tag'] = array_flip(explode(',', $params['tag'])); + CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $params['contact_id']); + } //save free tags if (isset($params['contact_taglist']) && !empty($params['contact_taglist'])) { CRM_Core_Form_Tag::postProcess($params['contact_taglist'], $params['contact_id'], 'civicrm_contact', $this); @@ -1011,7 +1039,7 @@ public function postProcess() { $session->setStatus($message, ts('Contact Saved'), 'success'); // add the recently viewed contact - $recentOther = array(); + $recentOther = []; if (($session->get('userID') == $contact->id) || CRM_Contact_BAO_Contact_Permission::allow($contact->id, CRM_Core_Permission::EDIT) ) { @@ -1040,7 +1068,7 @@ public function postProcess() { $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/add', $resetStr)); } else { - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this); //validate the qfKey $urlParams = 'reset=1&cid=' . $contact->id; @@ -1077,7 +1105,7 @@ public static function blockDataExists(&$fields) { return FALSE; } - static $skipFields = array( + static $skipFields = [ 'location_type_id', 'is_primary', 'phone_type_id', @@ -1085,7 +1113,7 @@ public static function blockDataExists(&$fields) { 'country_id', 'website_type_id', 'master_id', - ); + ]; foreach ($fields as $name => $value) { $skipField = FALSE; foreach ($skipFields as $skip) { @@ -1130,27 +1158,27 @@ public static function checkDuplicateContacts(&$fields, &$errors, $contactID, $c // if this is a forced save, ignore find duplicate rule if (empty($fields['_qf_Contact_upload_duplicate'])) { - $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, $contactType, 'Supervised', array($contactID)); + $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, $contactType, 'Supervised', [$contactID]); if ($ids) { $contactLinks = CRM_Contact_BAO_Contact_Utils::formatContactIDSToLinks($ids, TRUE, TRUE, $contactID); $duplicateContactsLinks = '
    '; - $duplicateContactsLinks .= ts('One matching contact was found. ', array( - 'count' => count($contactLinks['rows']), - 'plural' => '%count matching contacts were found.
    ', - )); + $duplicateContactsLinks .= ts('One matching contact was found. ', [ + 'count' => count($contactLinks['rows']), + 'plural' => '%count matching contacts were found.
    ', + ]); if ($contactLinks['msg'] == 'view') { - $duplicateContactsLinks .= ts('You can View the existing contact', array( - 'count' => count($contactLinks['rows']), - 'plural' => 'You can View the existing contacts', - )); + $duplicateContactsLinks .= ts('You can View the existing contact', [ + 'count' => count($contactLinks['rows']), + 'plural' => 'You can View the existing contacts', + ]); } else { - $duplicateContactsLinks .= ts('You can View or Edit the existing contact', array( - 'count' => count($contactLinks['rows']), - 'plural' => 'You can View or Edit the existing contacts', - )); + $duplicateContactsLinks .= ts('You can View or Edit the existing contact', [ + 'count' => count($contactLinks['rows']), + 'plural' => 'You can View or Edit the existing contacts', + ]); } if ($contactLinks['msg'] == 'merge') { // We should also get a merge link if this is for an existing contact @@ -1221,7 +1249,7 @@ public function getTemplateFileName() { * as array of success/fails for each address block */ public function parseAddress(&$params) { - $parseSuccess = $parsedFields = array(); + $parseSuccess = $parsedFields = []; if (!is_array($params['address']) || CRM_Utils_System::isNull($params['address']) ) { @@ -1231,11 +1259,11 @@ public function parseAddress(&$params) { foreach ($params['address'] as $instance => & $address) { $buildStreetAddress = FALSE; $parseFieldName = 'street_address'; - foreach (array( - 'street_number', - 'street_name', - 'street_unit', - ) as $fld) { + foreach ([ + 'street_number', + 'street_name', + 'street_unit', + ] as $fld) { if (!empty($address[$fld])) { $parseFieldName = 'street_number'; $buildStreetAddress = TRUE; @@ -1263,16 +1291,16 @@ public function parseAddress(&$params) { $address['street_number'] = $parsedFields['street_number']; $streetAddress = NULL; - foreach (array( - 'street_number', - 'street_number_suffix', - 'street_name', - 'street_unit', - ) as $fld) { - if (in_array($fld, array( + foreach ([ + 'street_number', + 'street_number_suffix', + 'street_name', + 'street_unit', + ] as $fld) { + if (in_array($fld, [ 'street_name', 'street_unit', - ))) { + ])) { $streetAddress .= ' '; } // CRM-17619 - if the street number suffix begins with a number, add a space @@ -1329,7 +1357,7 @@ public static function parseAddressStatusMsg($parseResult) { return $statusMsg; } - $parseFails = array(); + $parseFails = []; foreach ($parseResult as $instance => $success) { if (!$success) { $parseFails[] = self::ordinalNumber($instance); @@ -1338,7 +1366,7 @@ public static function parseAddressStatusMsg($parseResult) { if (!empty($parseFails)) { $statusMsg = ts("Complete street address(es) have been saved. However we were unable to split the address in the %1 address block(s) into address elements (street number, street name, street unit) due to an unrecognized address format. You can set the address elements manually by clicking 'Edit Address Elements' next to the Street Address field while in edit mode.", - array(1 => implode(', ', $parseFails)) + [1 => implode(', ', $parseFails)] ); } @@ -1435,7 +1463,7 @@ public function updateMembershipStatus($deceasedParams) { ); // add membership log - $membershipLog = array( + $membershipLog = [ 'membership_id' => $dao->id, 'status_id' => $deceasedStatusId, 'start_date' => CRM_Utils_Date::isoToMysql($dao->start_date), @@ -1444,12 +1472,12 @@ public function updateMembershipStatus($deceasedParams) { 'modified_date' => date('Ymd'), 'membership_type_id' => $dao->membership_type_id, 'max_related' => $dao->max_related, - ); + ]; - CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray); + CRM_Member_BAO_MembershipLog::add($membershipLog); //create activity when membership status is changed - $activityParam = array( + $activityParam = [ 'subject' => "Status changed from {$allStatus[$dao->status_id]} to {$allStatus[$deceasedStatusId]}", 'source_contact_id' => $userId, 'target_contact_id' => $dao->contact_id, @@ -1462,7 +1490,7 @@ public function updateMembershipStatus($deceasedParams) { 'is_auto' => 0, 'is_current_revision' => 1, 'is_deleted' => 0, - ); + ]; $activityResult = civicrm_api('activity', 'create', $activityParam); $memCount++; @@ -1471,7 +1499,7 @@ public function updateMembershipStatus($deceasedParams) { // set status msg if ($memCount) { $updateMembershipMsg = ts("%1 Current membership(s) for this contact have been set to 'Deceased' status.", - array(1 => $memCount) + [1 => $memCount] ); } } diff --git a/CRM/Contact/Form/CustomData.php b/CRM/Contact/Form/CustomData.php index a9aad3381c11..ddd06219bb0f 100644 --- a/CRM/Contact/Form/CustomData.php +++ b/CRM/Contact/Form/CustomData.php @@ -1,9 +1,9 @@ _groupID); $mode = CRM_Utils_Request::retrieve('mode', 'String', CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET'); $mode = ucfirst($mode); - CRM_Utils_System::setTitle(ts('%1 %2 Record', array(1 => $mode, 2 => $groupTitle))); + CRM_Utils_System::setTitle(ts('%1 %2 Record', [1 => $mode, 2 => $groupTitle])); if (!empty($_POST['hidden_custom'])) { $this->assign('postedInfo', TRUE); @@ -177,23 +177,22 @@ public function buildQuickForm() { if ($isMultiple) { $this->assign('multiRecordDisplay', $this->_multiRecordDisplay); $saveButtonName = $this->_copyValueId ? ts('Save a Copy') : ts('Save'); - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => $saveButtonName, - 'isDefault' => TRUE, - ), - array( - 'type' => 'upload', - 'name' => ts('Save and New'), - 'subName' => 'new', - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => $saveButtonName, + 'isDefault' => TRUE, + ], + [ + 'type' => 'upload', + 'name' => ts('Save and New'), + 'subName' => 'new', + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } } return CRM_Custom_Form_CustomData::buildQuickForm($this); @@ -205,18 +204,17 @@ public function buildQuickForm() { // make this form an upload since we dont know if the custom data injected dynamically // is of type file etc - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -231,10 +229,10 @@ public function setDefaultValues() { if ($this->_copyValueId) { // cached tree is fetched $groupTree = CRM_Core_BAO_CustomGroup::getTree($this->_type, - $this, + NULL, $this->_entityId, $this->_groupID, - array(), + [], NULL, TRUE, NULL, @@ -242,7 +240,7 @@ public function setDefaultValues() { TRUE, $this->_copyValueId ); - $valueIdDefaults = array(); + $valueIdDefaults = []; $groupTreeValueId = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, $this->_copyValueId, $this); CRM_Core_BAO_CustomGroup::setDefaults($groupTreeValueId, $valueIdDefaults, FALSE, FALSE, $this->get('action')); $tableId = $groupTreeValueId[$this->_groupID]['table_id']; @@ -261,7 +259,7 @@ public function setDefaultValues() { } $groupTree = CRM_Core_BAO_CustomGroup::getTree($this->_contactType, - $this, + NULL, $this->_tableID, $this->_groupID, $this->_contactSubType @@ -269,7 +267,7 @@ public function setDefaultValues() { if (empty($_POST['hidden_custom_group_count'])) { // custom data building in edit mode (required to handle multi-value) - $groupTree = CRM_Core_BAO_CustomGroup::getTree($this->_contactType, $this, $this->_tableID, + $groupTree = CRM_Core_BAO_CustomGroup::getTree($this->_contactType, NULL, $this->_tableID, $this->_groupID, $this->_contactSubType ); $customValueCount = CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, TRUE, $this->_groupID, NULL, NULL, $this->_tableID); @@ -280,7 +278,7 @@ public function setDefaultValues() { $this->assign('customValueCount', $customValueCount); - $defaults = array(); + $defaults = []; return $defaults; } diff --git a/CRM/Contact/Form/DedupeFind.php b/CRM/Contact/Form/DedupeFind.php index 155862b7a794..7f9ebaec2466 100644 --- a/CRM/Contact/Form/DedupeFind.php +++ b/CRM/Contact/Form/DedupeFind.php @@ -1,9 +1,9 @@ ts('- All Contacts -')) + CRM_Core_PseudoConstant::nestedGroup(); + $groupList = ['' => ts('- All Contacts -')] + CRM_Core_PseudoConstant::nestedGroup(); - $this->add('select', 'group_id', ts('Select Group'), $groupList, FALSE, array('class' => 'crm-select2 huge')); + $this->add('select', 'group_id', ts('Select Group'), $groupList, FALSE, ['class' => 'crm-select2 huge']); if (Civi::settings()->get('dedupe_default_limit')) { $this->add('text', 'limit', ts('No of contacts to find matches for ')); } - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Continue'), - 'isDefault' => TRUE, - ), - //hack to support cancel button functionality - array( - 'type' => 'submit', - 'class' => 'cancel', - 'icon' => 'fa-times', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Continue'), + 'isDefault' => TRUE, + ], + //hack to support cancel button functionality + [ + 'type' => 'submit', + 'class' => 'cancel', + 'icon' => 'fa-times', + 'name' => ts('Cancel'), + ], + ]); } /** diff --git a/CRM/Contact/Form/DedupeRules.php b/CRM/Contact/Form/DedupeRules.php index 82e2faf15ace..84dd28002b2c 100644 --- a/CRM/Contact/Form/DedupeRules.php +++ b/CRM/Contact/Form/DedupeRules.php @@ -1,9 +1,9 @@ _options = CRM_Core_SelectValues::getDedupeRuleTypes(); $this->_rgid = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE, 0); - $this->_contactType = CRM_Utils_Request::retrieve('contact_type', 'String', $this, FALSE, 0); + + // check if $contactType is valid + $contactTypes = civicrm_api3('Contact', 'getOptions', ['field' => "contact_type", 'context' => "validate"]); + $contactType = CRM_Utils_Request::retrieve('contact_type', 'String', $this, FALSE, 0); + if (!empty($contactTypes['values'][$contactType])) { + $this->_contactType = $contactType; + } + elseif (!empty($contactType)) { + throw new CRM_Core_Exception('Contact Type is Not valid'); + } if ($this->_rgid) { $rgDao = new CRM_Dedupe_DAO_RuleGroup(); $rgDao->id = $this->_rgid; @@ -98,38 +107,32 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addField('title', array('label' => ts('Rule Name')), TRUE); + $this->addField('title', ['label' => ts('Rule Name')], TRUE); $this->addRule('title', ts('A duplicate matching rule with this name already exists. Please select another name.'), - 'objectExists', array('CRM_Dedupe_DAO_RuleGroup', $this->_rgid, 'title') + 'objectExists', ['CRM_Dedupe_DAO_RuleGroup', $this->_rgid, 'title'] ); - $this->addField('used', array('label' => ts('Usage')), TRUE); - $disabled = array(); - $reserved = $this->addField('is_reserved', array('label' => ts('Reserved?'))); + $this->addField('used', ['label' => ts('Usage')], TRUE); + $reserved = $this->addField('is_reserved', ['label' => ts('Reserved?')]); if (!empty($this->_defaults['is_reserved'])) { $reserved->freeze(); } - $attributes = array('class' => 'two'); - if (!empty($disabled)) { - $attributes = array_merge($attributes, $disabled); - } + $attributes = ['class' => 'two']; for ($count = 0; $count < self::RULES_COUNT; $count++) { $this->add('select', "where_$count", ts('Field'), - array( - NULL => ts('- none -'), - ) + $this->_fields, FALSE, $disabled + $this->_fields, FALSE, ['class' => 'crm-select2', 'placeholder' => ts('Select Field')] ); - $this->addField("length_$count", array('entity' => 'Rule', 'name' => 'rule_length') + $attributes); - $this->addField("weight_$count", array('entity' => 'Rule', 'name' => 'rule_weight') + $attributes); + $this->addField("length_$count", ['entity' => 'Rule', 'name' => 'rule_length'] + $attributes); + $this->addField("weight_$count", ['entity' => 'Rule', 'name' => 'rule_weight'] + $attributes); } - $this->addField('threshold', array('label' => ts("Weight Threshold to Consider Contacts 'Matching':")) + $attributes); + $this->addField('threshold', ['label' => ts("Weight Threshold to Consider Contacts 'Matching':")] + $attributes); $this->assign('contact_type', $this->_contactType); - $this->addFormRule(array('CRM_Contact_Form_DedupeRules', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_DedupeRules', 'formRule'], $this); parent::buildQuickForm(); } @@ -147,7 +150,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; $fieldSelected = FALSE; for ($count = 0; $count < self::RULES_COUNT; $count++) { if (!empty($fields["where_$count"]) || (isset($self->_defaults['is_reserved']) && !empty($self->_defaults["where_$count"]))) { @@ -156,7 +159,11 @@ public static function formRule($fields, $files, $self) { } } if (empty($fields['threshold'])) { - $errors['threshold'] = ts('Threshold weight cannot be empty or zero.'); + // CRM-20607 - Don't validate the threshold of hard-coded rules + if (!(CRM_Utils_Array::value('is_reserved', $fields) && + CRM_Utils_File::isIncludable("CRM/Dedupe/BAO/QueryBuilder/{$self->_defaultValues['name']}.php"))) { + $errors['threshold'] = ts('Threshold weight cannot be empty or zero.'); + } } if (!$fieldSelected) { @@ -173,6 +180,7 @@ public static function formRule($fields, $files, $self) { * * @return array */ + /** * @return array */ @@ -195,10 +203,10 @@ public function postProcess() { SET used = 'General' WHERE contact_type = %1 AND used = %2"; - $queryParams = array( - 1 => array($this->_contactType, 'String'), - 2 => array($values['used'], 'String'), - ); + $queryParams = [ + 1 => [$this->_contactType, 'String'], + 2 => [$values['used'], 'String'], + ]; CRM_Core_DAO::executeQuery($query, $queryParams); } @@ -224,18 +232,16 @@ public function postProcess() { // lets skip updating of fields for reserved dedupe group if (CRM_Utils_Array::value('is_reserved', $this->_defaults)) { - CRM_Core_Session::setStatus(ts("The rule '%1' has been saved.", array(1 => $rgDao->title)), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts("The rule '%1' has been saved.", [1 => $rgDao->title]), ts('Saved'), 'success'); return; } $ruleDao = new CRM_Dedupe_DAO_Rule(); $ruleDao->dedupe_rule_group_id = $rgDao->id; $ruleDao->delete(); - $ruleDao->free(); - - $substrLenghts = array(); + $substrLenghts = []; - $tables = array(); + $tables = []; $daoObj = new CRM_Core_DAO(); $database = $daoObj->database(); for ($count = 0; $count < self::RULES_COUNT; $count++) { @@ -253,10 +259,9 @@ public function postProcess() { $ruleDao->rule_length = $length; $ruleDao->rule_weight = $weight; $ruleDao->save(); - $ruleDao->free(); if (!array_key_exists($table, $tables)) { - $tables[$table] = array(); + $tables[$table] = []; } $tables[$table][] = $field; } @@ -264,7 +269,7 @@ public function postProcess() { // CRM-6245: we must pass table/field/length triples to the createIndexes() call below if ($length) { if (!isset($substrLenghts[$table])) { - $substrLenghts[$table] = array(); + $substrLenghts[$table] = []; } //CRM-13417 to avoid fatal error "Incorrect prefix key; the used key part isn't a string, the used length is longer than the key part, or the storage engine doesn't support unique prefix keys, 1089" @@ -275,15 +280,14 @@ public function postProcess() { if ($dao->fetch()) { // set the length to null for all the fields where prefix length is not supported. eg. int,tinyint,date,enum etc dataTypes. - if ($dao->COLUMN_NAME == $field && !in_array($dao->DATA_TYPE, array( - 'char', - 'varchar', - 'binary', - 'varbinary', - 'text', - 'blob', - )) - ) { + if ($dao->COLUMN_NAME == $field && !in_array($dao->DATA_TYPE, [ + 'char', + 'varchar', + 'binary', + 'varbinary', + 'text', + 'blob', + ])) { $length = NULL; } elseif ($dao->COLUMN_NAME == $field && !empty($dao->CHARACTER_MAXIMUM_LENGTH) && ($length > $dao->CHARACTER_MAXIMUM_LENGTH)) { @@ -306,7 +310,7 @@ public function postProcess() { CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKey); - CRM_Core_Session::setStatus(ts("The rule '%1' has been saved.", array(1 => $rgDao->title)), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts("The rule '%1' has been saved.", [1 => $rgDao->title]), ts('Saved'), 'success'); } } diff --git a/CRM/Contact/Form/Domain.php b/CRM/Contact/Form/Domain.php index c041cef4c695..dcd9eb914677 100644 --- a/CRM/Contact/Form/Domain.php +++ b/CRM/Contact/Form/Domain.php @@ -1,9 +1,9 @@ _id)) { $params['id'] = $this->_id; CRM_Core_BAO_Domain::retrieve($params, $domainDefaults); $this->_contactId = $domainDefaults['contact_id']; - //get the default domain from email address. fix CRM-3552 - $optionValues = array(); - $grpParams['name'] = 'from_email_address'; - CRM_Core_OptionValue::getValues($grpParams, $optionValues); - foreach ($optionValues as $Id => $value) { - if ($value['is_default'] && $value['is_active']) { - $this->_fromEmailId = $Id; - $list = explode('"', $value['label']); - $domainDefaults['email_name'] = CRM_Utils_Array::value(1, $list); - $domainDefaults['email_address'] = CRM_Utils_Mail::pluckEmailFromHeader($value['label']); - break; - } - } unset($params['id']); - $locParams = array('contact_id' => $domainDefaults['contact_id']); + $locParams = ['contact_id' => $domainDefaults['contact_id']]; $this->_locationDefaults = $defaults = CRM_Core_BAO_Location::getValues($locParams); $config = CRM_Core_Config::singleton(); @@ -154,27 +141,24 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - $this->addField('name', array('label' => ts('Organization Name')), TRUE); - $this->addField('description', array('label' => ts('Description'), 'size' => 30)); - $this->add('text', 'email_name', ts('FROM Name'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email', 'email'), TRUE); - $this->add('text', 'email_address', ts('FROM Email Address'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email', 'email'), TRUE); - $this->addRule('email_address', ts('Domain Email Address must use a valid email address format (e.g. \'info@example.org\').'), 'email'); + $this->addField('name', ['label' => ts('Organization Name')], TRUE); + $this->addField('description', ['label' => ts('Description'), 'size' => 30]); //build location blocks. CRM_Contact_Form_Location::buildQuickForm($this); - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'next', 'name' => ts('Save'), 'subName' => 'view', 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); if ($this->_action & CRM_Core_Action::VIEW) { $this->freeze(); @@ -186,7 +170,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_Domain', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Domain', 'formRule']); } /** @@ -204,19 +188,9 @@ public static function formRule($fields) { // $errors === TRUE means no errors from above formRule excution, // so declaring $errors to array for further processing if ($errors === TRUE) { - $errors = array(); - } - - //fix for CRM-3552, - //as we use "fromName" format for domain email. - if (strpos($fields['email_name'], '"') !== FALSE) { - $errors['email_name'] = ts('Double quotes are not allow in from name.'); + $errors = []; } - // Check for default from email address and organization (domain) name. Force them to change it. - if ($fields['email_address'] == 'info@EXAMPLE.ORG') { - $errors['email_address'] = ts('Please enter a valid default FROM email address for system-generated emails.'); - } if ($fields['name'] == 'Default Domain Name') { $errors['name'] = ts('Please enter the name of the organization or entity which owns this CiviCRM site.'); } @@ -256,15 +230,15 @@ public function postProcess() { $params['email'][1]['location_type_id'] = $defaultLocationType->id; } - $params += array('contact_id' => $this->_contactId); - $contactParams = array( + $params += ['contact_id' => $this->_contactId]; + $contactParams = [ 'sort_name' => $domain->name, 'display_name' => $domain->name, 'legal_name' => $domain->name, 'organization_name' => $domain->name, 'contact_id' => $this->_contactId, 'contact_type' => 'Organization', - ); + ]; if ($this->_contactId) { $contactParams['contact_sub_type'] = CRM_Contact_BAO_Contact::getContactSubType($this->_contactId); @@ -275,37 +249,7 @@ public function postProcess() { CRM_Core_BAO_Domain::edit($params, $this->_id); - //set domain from email address, CRM-3552 - $emailName = '"' . $params['email_name'] . '" <' . $params['email_address'] . '>'; - - $emailParams = array( - 'label' => $emailName, - 'description' => $params['description'], - 'is_active' => 1, - 'is_default' => 1, - ); - - $groupParams = array('name' => 'from_email_address'); - - //get the option value wt. - if ($this->_fromEmailId) { - $action = $this->_action; - $emailParams['weight'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_fromEmailId, 'weight'); - } - else { - //add from email address. - $action = CRM_Core_Action::ADD; - $grpId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'from_email_address', 'id', 'name'); - $fieldValues = array('option_group_id' => $grpId); - $emailParams['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue', $fieldValues); - } - - //reset default within domain. - $emailParams['reset_default_for'] = array('domain_id' => CRM_Core_Config::domainID()); - - CRM_Core_OptionValue::addOptionValue($emailParams, $groupParams, $action, $this->_fromEmailId); - - CRM_Core_Session::setStatus(ts("Domain information for '%1' has been saved.", array(1 => $domain->name)), ts('Saved'), 'success'); + CRM_Core_Session::setStatus(ts("Domain information for '%1' has been saved.", [1 => $domain->name]), ts('Saved'), 'success'); $session = CRM_Core_Session::singleton(); $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin', 'reset=1')); } diff --git a/CRM/Contact/Form/Edit/Address.php b/CRM/Contact/Form/Edit/Address.php index f8a0a1a9bc83..21dbd8cf68d3 100644 --- a/CRM/Contact/Form/Edit/Address.php +++ b/CRM/Contact/Form/Edit/Address.php @@ -1,9 +1,9 @@ defaultContactCountry; - $form->applyFilter('__ALL__', 'trim'); - $js = array(); + $js = []; if (!$inlineEdit) { - $js = array('onChange' => 'checkLocation( this.id );', 'placeholder' => NULL); + $js = ['onChange' => 'checkLocation( this.id );', 'placeholder' => NULL]; } //make location type required for inline edit - $form->addField("address[$blockId][location_type_id]", array('entity' => 'address', 'class' => 'eight', 'option_url' => NULL) + $js, $inlineEdit); + $form->addField("address[$blockId][location_type_id]", ['entity' => 'address', 'class' => 'eight', 'option_url' => NULL] + $js, $inlineEdit); if (!$inlineEdit) { - $js = array('id' => 'Address_' . $blockId . '_IsPrimary', 'onClick' => 'singleSelect( this.id );'); + $js = ['id' => 'Address_' . $blockId . '_IsPrimary', 'onClick' => 'singleSelect( this.id );']; } $form->addField( - "address[$blockId][is_primary]", array( + "address[$blockId][is_primary]", [ 'entity' => 'address', 'label' => ts('Primary location for this contact'), - 'text' => ts('Primary location for this contact')) + $js); + 'text' => ts('Primary location for this contact'), + ] + $js); if (!$inlineEdit) { - $js = array('id' => 'Address_' . $blockId . '_IsBilling', 'onClick' => 'singleSelect( this.id );'); + $js = ['id' => 'Address_' . $blockId . '_IsBilling', 'onClick' => 'singleSelect( this.id );']; } $form->addField( - "address[$blockId][is_billing]", array( + "address[$blockId][is_billing]", [ 'entity' => 'address', 'label' => ts('Billing location for this contact'), - 'text' => ts('Billing location for this contact')) + $js); + 'text' => ts('Billing location for this contact'), + ] + $js); // hidden element to store master address id - $form->addField("address[$blockId][master_id]", array('entity' => 'address', 'type' => 'hidden')); + $form->addField("address[$blockId][master_id]", ['entity' => 'address', 'type' => 'hidden']); $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'address_options', TRUE, NULL, TRUE ); - $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address'); - $elements = array( + $elements = [ 'address_name', 'street_address', 'supplemental_address_1', @@ -112,34 +112,33 @@ public static function buildQuickForm(&$form, $addressBlockCount = NULL, $sharin 'street_number', 'street_name', 'street_unit', - ); + ]; foreach ($elements as $name) { - //Remove id from name, to allow comparison against enabled addressOtions. + //Remove id from name, to allow comparison against enabled addressOptions. $nameWithoutID = strpos($name, '_id') !== FALSE ? substr($name, 0, -3) : $name; // Skip fields which are not enabled in the address options. if (empty($addressOptions[$nameWithoutID])) { $continue = TRUE; //Don't skip street parsed fields when parsing is enabled. - if (in_array($nameWithoutID, array( - 'street_number', - 'street_name', - 'street_unit', - )) && !empty($addressOptions['street_address_parsing']) - ) { + if (in_array($nameWithoutID, [ + 'street_number', + 'street_name', + 'street_unit', + ]) && !empty($addressOptions['street_address_parsing'])) { $continue = FALSE; } if ($continue) { continue; } } - if ($name == 'address_name') { + if ($name === 'address_name') { $name = 'name'; } - $params = array('entity' => 'address'); + $params = ['entity' => 'address']; - if ($name == 'postal_code_suffix') { + if ($name === 'postal_code_suffix') { $params['label'] = ts('Suffix'); } @@ -153,7 +152,7 @@ public static function buildQuickForm(&$form, $addressBlockCount = NULL, $sharin // CRM-11665 geocode override option $geoCode = FALSE; - if (!empty($config->geocodeMethod)) { + if (CRM_Utils_GeocodeProvider::getUsableClassName()) { $geoCode = TRUE; $form->addElement('checkbox', "address[$blockId][manual_geo_code]", @@ -162,72 +161,18 @@ public static function buildQuickForm(&$form, $addressBlockCount = NULL, $sharin } $form->assign('geoCode', $geoCode); - // Process any address custom data - - $groupTree = CRM_Core_BAO_CustomGroup::getTree('Address', - $form, - $entityId - ); - - if (isset($groupTree) && is_array($groupTree)) { - // use simplified formatted groupTree - $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1, $form); - - // make sure custom fields are added /w element-name in the format - 'address[$blockId][custom-X]' - foreach ($groupTree as $id => $group) { - foreach ($group['fields'] as $fldId => $field) { - $groupTree[$id]['fields'][$fldId]['element_custom_name'] = $field['element_name']; - $groupTree[$id]['fields'][$fldId]['element_name'] = "address[$blockId][{$field['element_name']}]"; - } - } - - $defaults = array(); - CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults); - - // since we change element name for address custom data, we need to format the setdefault values - $addressDefaults = array(); - foreach ($defaults as $key => $val) { - if (empty($val)) { - continue; - } - - // inorder to set correct defaults for checkbox custom data, we need to converted flat key to array - // this works for all types custom data - $keyValues = explode('[', str_replace(']', '', $key)); - $addressDefaults[$keyValues[0]][$keyValues[1]][$keyValues[2]] = $val; - } - - $form->setDefaults($addressDefaults); - - // we setting the prefix to 'dnc_' below, so that we don't overwrite smarty's grouptree var. - // And we can't set it to 'address_' because we want to set it in a slightly different format. - CRM_Core_BAO_CustomGroup::buildQuickForm($form, $groupTree, FALSE, 'dnc_'); - - // during contact editing : if no address is filled - // required custom data must not produce 'required' form rule error - // more handling done in formRule func - CRM_Contact_Form_Edit_Address::storeRequiredCustomDataInfo($form, $groupTree); - - $template = CRM_Core_Smarty::singleton(); - $tplGroupTree = $template->get_template_vars('address_groupTree'); - $tplGroupTree = empty($tplGroupTree) ? array() : $tplGroupTree; - - $form->assign('address_groupTree', $tplGroupTree + array($blockId => $groupTree)); - // unset the temp smarty var that got created - $form->assign('dnc_groupTree', NULL); - } - // address custom data processing ends .. + self::addCustomDataToForm($form, $entityId, $blockId); if ($sharing) { // shared address $form->addElement('checkbox', "address[$blockId][use_shared_address]", NULL, ts('Use another contact\'s address')); // Override the default profile links to add address form - $profileLinks = CRM_Core_BAO_UFGroup::getCreateLinks(array( - 'new_individual', - 'new_organization', - 'new_household', - ), 'shared_address'); - $form->addEntityRef("address[$blockId][master_contact_id]", ts('Share With'), array('create' => $profileLinks)); + $profileLinks = CRM_Contact_BAO_Contact::getEntityRefCreateLinks('shared_address'); + $form->addEntityRef("address[$blockId][master_contact_id]", ts('Share With'), ['create' => $profileLinks, 'api' => ['extra' => ['contact_type']]]); + + // do we want to update employer for shared address + $form->addElement('checkbox', "address[$blockId][update_current_employer]", NULL, ts('Set this organization as current employer')); } } @@ -241,10 +186,10 @@ public static function buildQuickForm(&$form, $addressBlockCount = NULL, $sharin * @return array|bool * if no errors */ - public static function formRule($fields, $files = array(), $self = NULL) { - $errors = array(); + public static function formRule($fields, $files = [], $self = NULL) { + $errors = []; - $customDataRequiredFields = array(); + $customDataRequiredFields = []; if ($self && property_exists($self, '_addressRequireOmission')) { $customDataRequiredFields = explode(',', $self->_addressRequireOmission); } @@ -298,14 +243,14 @@ public static function formRule($fields, $files = array(), $self = NULL) { * Form object. */ public static function setDefaultValues(&$defaults, &$form) { - $addressValues = array(); + $addressValues = []; if (isset($defaults['address']) && is_array($defaults['address']) && !CRM_Utils_System::isNull($defaults['address']) ) { // start of contact shared adddress defaults - $sharedAddresses = array(); - $masterAddress = array(); + $sharedAddresses = []; + $masterAddress = []; // get contact name of shared contact names $shareAddressContactNames = CRM_Contact_BAO_Contact_Utils::getAddressShareContactNames($defaults['address']); @@ -313,15 +258,15 @@ public static function setDefaultValues(&$defaults, &$form) { foreach ($defaults['address'] as $key => $addressValue) { if (!empty($addressValue['master_id']) && !$shareAddressContactNames[$addressValue['master_id']]['is_deleted']) { $master_cid = $shareAddressContactNames[$addressValue['master_id']]['contact_id']; - $sharedAddresses[$key]['shared_address_display'] = array( + $sharedAddresses[$key]['shared_address_display'] = [ 'address' => $addressValue['display'], 'name' => $shareAddressContactNames[$addressValue['master_id']]['name'], - 'options' => CRM_Core_BAO_Address::getValues(array( - 'entity_id' => $master_cid, - 'contact_id' => $master_cid, - )), + 'options' => CRM_Core_BAO_Address::getValues([ + 'entity_id' => $master_cid, + 'contact_id' => $master_cid, + ]), 'master_id' => $addressValue['master_id'], - ); + ]; $defaults['address'][$key]['master_contact_id'] = $master_cid; } else { @@ -339,19 +284,19 @@ public static function setDefaultValues(&$defaults, &$form) { // start of parse address functionality // build street address, CRM-5450. if ($form->_parseStreetAddress) { - $parseFields = array('street_address', 'street_number', 'street_name', 'street_unit'); + $parseFields = ['street_address', 'street_number', 'street_name', 'street_unit']; foreach ($defaults['address'] as $cnt => & $address) { $streetAddress = NULL; - foreach (array( - 'street_number', - 'street_number_suffix', - 'street_name', - 'street_unit', - ) as $fld) { - if (in_array($fld, array( + foreach ([ + 'street_number', + 'street_number_suffix', + 'street_name', + 'street_unit', + ] as $fld) { + if (in_array($fld, [ 'street_name', 'street_unit', - ))) { + ])) { $streetAddress .= ' '; } // CRM-17619 - if the street number suffix begins with a number, add a space @@ -382,7 +327,7 @@ public static function setDefaultValues(&$defaults, &$form) { $addressValues["{$field}_{$cnt}"] = CRM_Utils_Array::value($field, $address); } // don't load fields, use js to populate. - foreach (array('street_number', 'street_name', 'street_unit') as $f) { + foreach (['street_number', 'street_name', 'street_unit'] as $f) { if (isset($address[$f])) { unset($address[$f]); } @@ -391,12 +336,12 @@ public static function setDefaultValues(&$defaults, &$form) { $form->assign('allAddressFieldValues', json_encode($addressValues)); //hack to handle show/hide address fields. - $parsedAddress = array(); + $parsedAddress = []; if ($form->_contactId && !empty($_POST['address']) && is_array($_POST['address']) ) { foreach ($_POST['address'] as $cnt => $values) { $showField = 'streetAddress'; - foreach (array('street_number', 'street_name', 'street_unit') as $fld) { + foreach (['street_number', 'street_name', 'street_unit'] as $fld) { if (!empty($values[$fld])) { $showField = 'addressElements'; break; @@ -419,16 +364,16 @@ public static function setDefaultValues(&$defaults, &$form) { * @param array $groupTree */ public static function storeRequiredCustomDataInfo(&$form, $groupTree) { - if (in_array(CRM_Utils_System::getClassName($form), array('CRM_Contact_Form_Contact', 'CRM_Contact_Form_Inline_Address'))) { + if (in_array(CRM_Utils_System::getClassName($form), ['CRM_Contact_Form_Contact', 'CRM_Contact_Form_Inline_Address'])) { $requireOmission = NULL; foreach ($groupTree as $csId => $csVal) { // only process Address entity fields - if ($csVal['extends'] != 'Address') { + if ($csVal['extends'] !== 'Address') { continue; } foreach ($csVal['fields'] as $cdId => $cdVal) { - if ($cdVal['is_required']) { + if (!empty($cdVal['is_required'])) { $elementName = $cdVal['element_name']; if (in_array($elementName, $form->_required)) { // store the omitted rule for a element, to be used later on @@ -442,4 +387,67 @@ public static function storeRequiredCustomDataInfo(&$form, $groupTree) { } } + /** + * Add custom data to the form. + * + * @param CRM_Core_Form $form + * @param int $entityId + * @param int $blockId + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + protected static function addCustomDataToForm(&$form, $entityId, $blockId) { + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Address', NULL, $entityId); + + if (isset($groupTree) && is_array($groupTree)) { + // use simplified formatted groupTree + $groupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1, $form); + + // make sure custom fields are added /w element-name in the format - 'address[$blockId][custom-X]' + foreach ($groupTree as $id => $group) { + foreach ($group['fields'] as $fldId => $field) { + $groupTree[$id]['fields'][$fldId]['element_custom_name'] = $field['element_name']; + $groupTree[$id]['fields'][$fldId]['element_name'] = "address[$blockId][{$field['element_name']}]"; + } + } + + $defaults = []; + CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults); + + // since we change element name for address custom data, we need to format the setdefault values + $addressDefaults = []; + foreach ($defaults as $key => $val) { + if (!isset($val)) { + continue; + } + + // in order to set correct defaults for checkbox custom data, we need to converted flat key to array + // this works for all types custom data + $keyValues = explode('[', str_replace(']', '', $key)); + $addressDefaults[$keyValues[0]][$keyValues[1]][$keyValues[2]] = $val; + } + + $form->setDefaults($addressDefaults); + + // we setting the prefix to 'dnc_' below, so that we don't overwrite smarty's grouptree var. + // And we can't set it to 'address_' because we want to set it in a slightly different format. + CRM_Core_BAO_CustomGroup::buildQuickForm($form, $groupTree, FALSE, 'dnc_'); + + // during contact editing : if no address is filled + // required custom data must not produce 'required' form rule error + // more handling done in formRule func + CRM_Contact_Form_Edit_Address::storeRequiredCustomDataInfo($form, $groupTree); + + $tplGroupTree = CRM_Core_Smarty::singleton() + ->get_template_vars('address_groupTree'); + $tplGroupTree = empty($tplGroupTree) ? [] : $tplGroupTree; + + $form->assign('address_groupTree', $tplGroupTree + [$blockId => $groupTree]); + // unset the temp smarty var that got created + $form->assign('dnc_groupTree', NULL); + } + // address custom data processing ends .. + } + } diff --git a/CRM/Contact/Form/Edit/CommunicationPreferences.php b/CRM/Contact/Form/Edit/CommunicationPreferences.php index 2dae66963669..91d125fa1ea2 100644 --- a/CRM/Contact/Form/Edit/CommunicationPreferences.php +++ b/CRM/Contact/Form/Edit/CommunicationPreferences.php @@ -1,9 +1,9 @@ addGroup($privacy, 'privacy', ts('Privacy'), ' 
    '); // preferred communication method - $comm = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method', array('loclize' => TRUE)); + $comm = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method', ['loclize' => TRUE]); foreach ($comm as $value => $title) { $commPreff[] = $form->createElement('advcheckbox', $value, NULL, $title); } - $form->addField('preferred_communication_method', array('entity' => 'contact', 'type' => 'CheckBoxGroup')); - $form->addField('preferred_language', array('entity' => 'contact')); + $form->addField('preferred_communication_method', ['entity' => 'contact', 'type' => 'CheckBoxGroup']); + $form->addField('preferred_language', ['entity' => 'contact']); if (!empty($privacyOptions)) { $commPreference['privacy'] = $privacyOptions; @@ -84,27 +84,27 @@ public static function buildQuickForm(&$form) { //using for display purpose. $form->assign('commPreference', $commPreference); - $form->addField('preferred_mail_format', array('entity' => 'contact', 'label' => ts('Email Format'))); + $form->addField('preferred_mail_format', ['entity' => 'contact', 'label' => ts('Email Format')]); - $form->addField('is_opt_out', array('entity' => 'contact', 'label' => ts('NO BULK EMAILS (User Opt Out)'))); + $form->addField('is_opt_out', ['entity' => 'contact', 'label' => ts('NO BULK EMAILS (User Opt Out)')]); - $form->addField('communication_style_id', array('entity' => 'contact', 'type' => 'RadioGroup')); + $form->addField('communication_style_id', ['entity' => 'contact', 'type' => 'RadioGroup']); //check contact type and build filter clause accordingly for greeting types, CRM-4575 $greetings = self::getGreetingFields($form->_contactType); foreach ($greetings as $greeting => $fields) { - $filter = array( + $filter = [ 'contact_type' => $form->_contactType, 'greeting_type' => $greeting, - ); + ]; //add addressee in Contact form $greetingTokens = CRM_Core_PseudoConstant::greeting($filter); if (!empty($greetingTokens)) { $form->addElement('select', $fields['field'], $fields['label'], - array( + [ '' => ts('- select -'), - ) + $greetingTokens + ] + $greetingTokens ); //custom addressee $form->addElement('text', $fields['customField'], $fields['customLabel'], @@ -131,10 +131,10 @@ public static function formRule($fields, $files, $self) { $greetings = self::getGreetingFields($self->_contactType); foreach ($greetings as $greeting => $details) { - $customizedValue = CRM_Core_OptionGroup::getValue($greeting, 'Customized', 'name'); + $customizedValue = CRM_Core_PseudoConstant::getKey('CRM_Contact_BAO_Contact', $details['field'], 'Customized'); if (CRM_Utils_Array::value($details['field'], $fields) == $customizedValue && empty($fields[$details['customField']])) { $errors[$details['customField']] = ts('Custom %1 is a required field if %1 is of type Customized.', - array(1 => $details['label']) + [1 => $details['label']] ); } } @@ -202,36 +202,36 @@ public static function setDefaultValues(&$form, &$defaults) { */ public static function getGreetingFields($contactType) { if (empty(self::$greetings[$contactType])) { - self::$greetings[$contactType] = array(); + self::$greetings[$contactType] = []; - $js = array( + $js = [ 'onfocus' => "if (!this.value) { this.value='Dear ';} else return false", 'onblur' => "if ( this.value == 'Dear') { this.value='';} else return false", - ); + ]; - self::$greetings[$contactType] = array( - 'addressee' => array( + self::$greetings[$contactType] = [ + 'addressee' => [ 'field' => 'addressee_id', 'customField' => 'addressee_custom', 'label' => ts('Addressee'), 'customLabel' => ts('Custom Addressee'), 'js' => NULL, - ), - 'email_greeting' => array( + ], + 'email_greeting' => [ 'field' => 'email_greeting_id', 'customField' => 'email_greeting_custom', 'label' => ts('Email Greeting'), 'customLabel' => ts('Custom Email Greeting'), 'js' => $js, - ), - 'postal_greeting' => array( + ], + 'postal_greeting' => [ 'field' => 'postal_greeting_id', 'customField' => 'postal_greeting_custom', 'label' => ts('Postal Greeting'), 'customLabel' => ts('Custom Postal Greeting'), 'js' => $js, - ), - ); + ], + ]; } return self::$greetings[$contactType]; diff --git a/CRM/Contact/Form/Edit/CustomData.php b/CRM/Contact/Form/Edit/CustomData.php index eb865796ed78..3833b288e6c7 100644 --- a/CRM/Contact/Form/Edit/CustomData.php +++ b/CRM/Contact/Form/Edit/CustomData.php @@ -1,9 +1,9 @@ addField('gender_id', array('entity' => 'contact', 'type' => 'Radio', 'allowClear' => TRUE)); + $form->addField('gender_id', ['entity' => 'contact', 'type' => 'Radio', 'allowClear' => TRUE]); - $form->addField('birth_date', array('entity' => 'contact'), FALSE, FALSE); + $form->addField('birth_date', ['entity' => 'contact'], FALSE, FALSE); - $form->addField('is_deceased', array('entity' => 'contact', 'label' => ts('Contact is Deceased'), 'onclick' => "showDeceasedDate()")); - $form->addField('deceased_date', array('entity' => 'contact'), FALSE, FALSE); + $form->addField('is_deceased', ['entity' => 'contact', 'label' => ts('Contact is Deceased'), 'onclick' => "showDeceasedDate()"]); + $form->addField('deceased_date', ['entity' => 'contact'], FALSE, FALSE); } /** diff --git a/CRM/Contact/Form/Edit/Email.php b/CRM/Contact/Form/Edit/Email.php index 9d6b217349c8..91ddeffea012 100644 --- a/CRM/Contact/Form/Edit/Email.php +++ b/CRM/Contact/Form/Edit/Email.php @@ -1,9 +1,9 @@ applyFilter('__ALL__', 'trim'); //Email box - $form->addField("email[$blockId][email]", array('entity' => 'email')); + $form->addField("email[$blockId][email]", ['entity' => 'email', 'aria-label' => ts('Email %1', [1 => $blockId])]); $form->addRule("email[$blockId][email]", ts('Email is not valid.'), 'email'); if (isset($form->_contactType) || $blockEdit) { //Block type - $form->addField("email[$blockId][location_type_id]", array('entity' => 'email', 'placeholder' => NULL, 'class' => 'eight', 'option_url' => NULL)); + $form->addField("email[$blockId][location_type_id]", ['entity' => 'email', 'placeholder' => NULL, 'class' => 'eight', 'option_url' => NULL]); //TODO: Refactor on_hold field to select. $multipleBulk = CRM_Core_BAO_Email::isMultipleBulkMail(); //On-hold select if ($multipleBulk) { - $holdOptions = array( + $holdOptions = [ 0 => ts('- select -'), 1 => ts('On Hold Bounce'), 2 => ts('On Hold Opt Out'), - ); + ]; $form->addElement('select', "email[$blockId][on_hold]", '', $holdOptions); } else { - $form->addField("email[$blockId][on_hold]", array('entity' => 'email', 'type' => 'advcheckbox')); + $form->addField("email[$blockId][on_hold]", ['entity' => 'email', 'type' => 'advcheckbox', 'aria-label' => ts('On Hold for Email %1?', [1 => $blockId])]); } //Bulkmail checkbox $form->assign('multipleBulk', $multipleBulk); - if ($multipleBulk) { - $js = array('id' => "Email_" . $blockId . "_IsBulkmail"); - $form->addElement('advcheckbox', "email[$blockId][is_bulkmail]", NULL, '', $js); - } - else { - $js = array('id' => "Email_" . $blockId . "_IsBulkmail"); - if (!$blockEdit) { - $js['onClick'] = 'singleSelect( this.id );'; - } - $form->addElement('radio', "email[$blockId][is_bulkmail]", '', '', '1', $js); + $js = ['id' => "Email_" . $blockId . "_IsBulkmail" , 'aria-label' => ts('Bulk Mailing for Email %1?', [1 => $blockId])]; + if (!$blockEdit) { + $js['onClick'] = 'singleSelect( this.id );'; } + $form->addElement('advcheckbox', "email[$blockId][is_bulkmail]", NULL, '', $js); //is_Primary radio - $js = array('id' => "Email_" . $blockId . "_IsPrimary"); + $js = ['id' => "Email_" . $blockId . "_IsPrimary", 'aria-label' => ts('Email %1 is primary?', [1 => $blockId])]; if (!$blockEdit) { $js['onClick'] = 'singleSelect( this.id );'; } @@ -105,11 +99,11 @@ public static function buildQuickForm(&$form, $blockCount = NULL, $blockEdit = F if (CRM_Utils_System::getClassName($form) == 'CRM_Contact_Form_Contact') { $form->add('textarea', "email[$blockId][signature_text]", ts('Signature (Text)'), - array('rows' => 2, 'cols' => 40) + ['rows' => 2, 'cols' => 40] ); $form->add('wysiwyg', "email[$blockId][signature_html]", ts('Signature (HTML)'), - array('rows' => 2, 'cols' => 40) + ['rows' => 2, 'cols' => 40] ); } } diff --git a/CRM/Contact/Form/Edit/Household.php b/CRM/Contact/Form/Edit/Household.php index 1b6823657e63..1d9fc60fdf3e 100644 --- a/CRM/Contact/Form/Edit/Household.php +++ b/CRM/Contact/Form/Edit/Household.php @@ -1,9 +1,9 @@ addField('nick_name'); - $form->addField('contact_source', array('label' => ts('Source'))); + $form->addField('contact_source', ['label' => ts('Source')]); } if (!$inlineEditMode) { - $form->addField('external_identifier', array('label' => ts('External ID'))); + $form->addField('external_identifier', ['label' => ts('External ID')]); $form->addRule('external_identifier', ts('External ID already exists in Database.'), 'objectExists', - array('CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier') + ['CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier'] ); } } @@ -84,17 +84,14 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) { * $error */ public static function formRule($fields, $files, $contactID = NULL) { - $errors = array(); - $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID); + $errors = []; + $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Household'); // make sure that household name is set if (empty($fields['household_name'])) { $errors['household_name'] = 'Household Name should be set.'; } - //check for duplicate - dedupe rules - CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Household'); - return empty($errors) ? TRUE : $errors; } diff --git a/CRM/Contact/Form/Edit/IM.php b/CRM/Contact/Form/Edit/IM.php index ca56642b398f..78781d8c2242 100644 --- a/CRM/Contact/Form/Edit/IM.php +++ b/CRM/Contact/Form/Edit/IM.php @@ -1,9 +1,9 @@ applyFilter('__ALL__', 'trim'); //IM provider select - $form->addField("im[$blockId][provider_id]", array('entity' => 'im', 'class' => 'eight', 'placeholder' => NULL)); + $form->addField("im[$blockId][provider_id]", ['entity' => 'im', 'class' => 'eight', 'placeholder' => NULL]); //Block type select - $form->addField("im[$blockId][location_type_id]", array('entity' => 'im', 'class' => 'eight', 'placeholder' => NULL, 'option_url' => NULL)); + $form->addField("im[$blockId][location_type_id]", ['entity' => 'im', 'class' => 'eight', 'placeholder' => NULL, 'option_url' => NULL]); //IM box - $form->addField("im[$blockId][name]", array('entity' => 'im')); + $form->addField("im[$blockId][name]", ['entity' => 'im', 'aria-label' => ts('Instant Messenger %1', [1 => $blockId])]); //is_Primary radio - $js = array('id' => 'IM_' . $blockId . '_IsPrimary'); + $js = ['id' => 'IM_' . $blockId . '_IsPrimary', 'aria-label' => ts('Instant Messenger %1 is primary?', [1 => $blockId])]; if (!$blockEdit) { $js['onClick'] = 'singleSelect( this.id );'; } diff --git a/CRM/Contact/Form/Edit/Individual.php b/CRM/Contact/Form/Edit/Individual.php index 0395b783403d..d39c7d95e8e1 100644 --- a/CRM/Contact/Form/Edit/Individual.php +++ b/CRM/Contact/Form/Edit/Individual.php @@ -1,9 +1,9 @@ 'eight', 'placeholder' => ' ', 'label' => $name == 'prefix_id' ? ts('Prefix') : ts('Suffix')); + $props = ['class' => 'eight', 'placeholder' => ' ', 'label' => $name == 'prefix_id' ? ts('Prefix') : ts('Suffix')]; } $form->addField($name, $props); } @@ -84,32 +84,25 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) { // job title // override the size for UI to look better - $form->addField('job_title', array('size' => '30')); + $form->addField('job_title', ['size' => '30']); //Current Employer Element - $props = array( - 'api' => array('params' => array('contact_type' => 'Organization')), + $props = [ + 'api' => ['params' => ['contact_type' => 'Organization']], 'create' => TRUE, - ); + ]; $form->addField('employer_id', $props); - $form->addField('contact_source', array('class' => 'big')); + $form->addField('contact_source', ['class' => 'big']); } if (!$inlineEditMode) { - $checkSimilar = Civi::settings()->get('contact_ajax_check_similar'); - - if ($checkSimilar == NULL) { - $checkSimilar = 0; - } - $form->assign('checkSimilar', $checkSimilar); - //External Identifier Element - $form->addField('external_identifier', array('label' => 'External ID')); + $form->addField('external_identifier', ['label' => 'External ID']); $form->addRule('external_identifier', ts('External ID already exists in Database.'), 'objectExists', - array('CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier') + ['CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier'] ); CRM_Core_ShowHideBlocks::links($form, 'demographics', '', ''); } @@ -128,17 +121,14 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) { * TRUE if no errors, else array of errors. */ public static function formRule($fields, $files, $contactID = NULL) { - $errors = array(); - $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID); + $errors = []; + $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Individual'); // make sure that firstName and lastName or a primary OpenID is set if (!$primaryID && (empty($fields['first_name']) || empty($fields['last_name']))) { $errors['_qf_default'] = ts('First Name and Last Name OR an email OR an OpenID in the Primary Location should be set.'); } - //check for duplicate - dedupe rules - CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Individual'); - return empty($errors) ? TRUE : $errors; } diff --git a/CRM/Contact/Form/Edit/Lock.php b/CRM/Contact/Form/Edit/Lock.php index b8119602c117..c844b8cea5e4 100644 --- a/CRM/Contact/Form/Edit/Lock.php +++ b/CRM/Contact/Form/Edit/Lock.php @@ -1,9 +1,9 @@ addField('modified_date', array('type' => 'hidden', 'id' => 'modified_date', 'label' => '')); + $form->addField('modified_date', ['type' => 'hidden', 'id' => 'modified_date', 'label' => '']); } /** @@ -59,7 +59,7 @@ public static function buildQuickForm(&$form) { * true if no errors, else array of errors */ public static function formRule($fields, $files, $contactID = NULL) { - $errors = array(); + $errors = []; $timestamps = CRM_Contact_BAO_Contact::getTimestamps($contactID); if ($fields['modified_date'] != $timestamps['modified_date']) { diff --git a/CRM/Contact/Form/Edit/Notes.php b/CRM/Contact/Form/Edit/Notes.php index 7da39198827c..d926847685d7 100644 --- a/CRM/Contact/Form/Edit/Notes.php +++ b/CRM/Contact/Form/Edit/Notes.php @@ -1,9 +1,9 @@ applyFilter('__ALL__', 'trim'); - $form->addField('subject', array('entity' => 'note', 'size' => '60')); - $form->addField('note', array('entity' => 'note', 'rows' => 3)); + $form->addField('subject', ['entity' => 'note', 'size' => '60']); + $form->addField('note', ['entity' => 'note', 'rows' => 3]); } } diff --git a/CRM/Contact/Form/Edit/OpenID.php b/CRM/Contact/Form/Edit/OpenID.php index de13a9657b5c..2d4ab2ca0c6d 100644 --- a/CRM/Contact/Form/Edit/OpenID.php +++ b/CRM/Contact/Form/Edit/OpenID.php @@ -1,9 +1,9 @@ addElement('select', "openid[$blockId][location_type_id]", '', CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id')); //is_Primary radio - $js = array('id' => "OpenID_" . $blockId . "_IsPrimary"); + $js = ['id' => "OpenID_" . $blockId . "_IsPrimary"]; if (!$blockEdit) { $js['onClick'] = 'singleSelect( this.id );'; } diff --git a/CRM/Contact/Form/Edit/Organization.php b/CRM/Contact/Form/Edit/Organization.php index a52bbd0047ad..05ae544a4f9d 100644 --- a/CRM/Contact/Form/Edit/Organization.php +++ b/CRM/Contact/Form/Edit/Organization.php @@ -1,9 +1,9 @@ addField('external_identifier', array('label' => ts('External ID'))); + $form->addField('external_identifier', ['label' => ts('External ID')]); $form->addRule('external_identifier', ts('External ID already exists in Database.'), 'objectExists', - array('CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier') + ['CRM_Contact_DAO_Contact', $form->_contactId, 'external_identifier'] ); } } @@ -85,17 +85,14 @@ public static function buildQuickForm(&$form, $inlineEditMode = NULL) { * @return array|bool */ public static function formRule($fields, $files, $contactID = NULL) { - $errors = array(); - $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID); + $errors = []; + $primaryID = CRM_Contact_Form_Contact::formRule($fields, $errors, $contactID, 'Organization'); // make sure that organization name is set if (empty($fields['organization_name'])) { $errors['organization_name'] = 'Organization Name should be set.'; } - //check for duplicate - dedupe rules - CRM_Contact_Form_Contact::checkDuplicateContacts($fields, $errors, $contactID, 'Organization'); - // add code to make sure that the uniqueness criteria is satisfied return empty($errors) ? TRUE : $errors; } diff --git a/CRM/Contact/Form/Edit/Phone.php b/CRM/Contact/Form/Edit/Phone.php index 84b7ccb379a3..79538fbaf977 100644 --- a/CRM/Contact/Form/Edit/Phone.php +++ b/CRM/Contact/Form/Edit/Phone.php @@ -1,9 +1,9 @@ applyFilter('__ALL__', 'trim'); //phone type select - $form->addField("phone[$blockId][phone_type_id]", array( + $form->addField("phone[$blockId][phone_type_id]", [ 'entity' => 'phone', 'class' => 'eight', 'placeholder' => NULL, - )); + ]); //main phone number with crm_phone class - $form->addField("phone[$blockId][phone]", array('entity' => 'phone', 'class' => 'crm_phone twelve')); - $form->addField("phone[$blockId][phone_ext]", array('entity' => 'phone')); + $form->addField("phone[$blockId][phone]", ['entity' => 'phone', 'class' => 'crm_phone twelve', 'aria-label' => ts('Phone %1', [1 => $blockId])]); + $form->addField("phone[$blockId][phone_ext]", ['entity' => 'phone', 'aria-label' => ts('Phone Extension %1', [1 => $blockId])]); if (isset($form->_contactType) || $blockEdit) { //Block type select - $form->addField("phone[$blockId][location_type_id]", array( + $form->addField("phone[$blockId][location_type_id]", [ 'entity' => 'phone', - 'class' => 'eight', - 'placeholder' => NULL, - 'option_url' => NULL, - )); + 'class' => 'eight', + 'placeholder' => NULL, + 'option_url' => NULL, + ]); //is_Primary radio - $js = array('id' => 'Phone_' . $blockId . '_IsPrimary', 'onClick' => 'singleSelect( this.id );'); + $js = ['id' => 'Phone_' . $blockId . '_IsPrimary', 'onClick' => 'singleSelect( this.id );', 'aria-label' => ts('Phone %1 is primary?', [1 => $blockId])]; $form->addElement('radio', "phone[$blockId][is_primary]", '', '', '1', $js); } // TODO: set this up as a group, we need a valid phone_type_id if we have a phone number diff --git a/CRM/Contact/Form/Edit/TagsAndGroups.php b/CRM/Contact/Form/Edit/TagsAndGroups.php index def736d726e8..b89c3a023e9e 100644 --- a/CRM/Contact/Form/Edit/TagsAndGroups.php +++ b/CRM/Contact/Form/Edit/TagsAndGroups.php @@ -1,9 +1,9 @@ _tagGroup)) { - $form->_tagGroup = array(); + $form->_tagGroup = []; } // NYSS 5670 @@ -91,7 +91,7 @@ public static function buildQuickForm( $groupID = isset($form->_grid) ? $form->_grid : NULL; if ($groupID && $visibility) { - $ids = array($groupID => $groupID); + $ids = [$groupID => $groupID]; } else { if ($visibility) { @@ -107,8 +107,8 @@ public static function buildQuickForm( $groups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids); $attributes['skiplabel'] = TRUE; - $elements = array(); - $groupsOptions = array(); + $elements = []; + $groupsOptions = []; foreach ($groups as $id => $group) { // make sure that this group has public visibility if ($visibility && @@ -128,7 +128,7 @@ public static function buildQuickForm( if ($groupElementType == 'select' && !empty($groupsOptions)) { $form->add('select', $fName, $groupName, $groupsOptions, FALSE, - array('id' => $fName, 'multiple' => 'multiple', 'class' => 'crm-select2') + ['id' => $fName, 'multiple' => 'multiple', 'class' => 'crm-select2 twenty'] ); $form->assign('groupCount', count($groupsOptions)); } @@ -137,7 +137,7 @@ public static function buildQuickForm( $form->addGroup($elements, $fName, $groupName, ' 
    '); $form->assign('groupCount', count($elements)); if ($isRequired) { - $form->addRule($fName, ts('%1 is a required field.', array(1 => $groupName)), 'required'); + $form->addRule($fName, ts('%1 is a required field.', [1 => $groupName]), 'required'); } } $form->assign('groupElementType', $groupElementType); @@ -145,40 +145,10 @@ public static function buildQuickForm( } if ($type & self::TAG) { - $fName = 'tag'; - if ($fieldName) { - $fName = $fieldName; - } - $form->_tagGroup[$fName] = 1; - - // get the list of all the categories - $tags = new CRM_Core_BAO_Tag(); - $tree = $tags->getTree('civicrm_contact', TRUE); - // let's not load jstree if there are not children. This also fixes blank - // display at the beginning of checkboxes - $loadJsTree = CRM_Utils_Array::retrieveValueRecursive($tree, 'children'); - $form->assign('loadjsTree', FALSE); - if (!empty($loadJsTree)) { - // CODE FROM CRM/Tag/Form/Tag.php // - CRM_Core_Resources::singleton() - ->addScriptFile('civicrm', 'packages/jquery/plugins/jstree/jquery.jstree.js', 0, 'html-header', FALSE) - ->addStyleFile('civicrm', 'packages/jquery/plugins/jstree/themes/default/style.css', 0, 'html-header'); - $form->assign('loadjsTree', TRUE); - } - - $elements = array(); - self::climbtree($form, $tree, $elements); + $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_contact'); - $form->addGroup($elements, $fName, $tagName, '
    '); - $form->assign('tagCount', count($elements)); - $form->assign('tree', $tree); - $form->assign('tag', $tree); - $form->assign('entityID', $contactId); - $form->assign('entityTable', 'civicrm_contact'); - $form->assign('allTags', CRM_Core_BAO_Tag::getTagsUsedFor('civicrm_contact', FALSE)); - - if ($isRequired) { - $form->addRule($fName, ts('%1 is a required field.', array(1 => $tagName)), 'required'); + if (!empty($tags)) { + $form->add('select2', 'tag', ts('Tag(s)'), $tags, FALSE, ['class' => 'huge', 'placeholder' => ts('- select -'), 'multiple' => TRUE]); } // build tag widget @@ -188,31 +158,6 @@ public static function buildQuickForm( $form->assign('tagGroup', $form->_tagGroup); } - /** - * Climb tree. - * - * @param $form - * @param $tree - * @param $elements - * - * @return mixed - */ - public static function climbtree($form, $tree, &$elements) { - foreach ($tree as $tagID => $varValue) { - $tagAttribute = array( - 'onclick' => "return changeRowColor(\"rowidtag_$tagID\")", - 'id' => "tag_{$tagID}", - ); - - $elements[$tagID] = $form->createElement('checkbox', $tagID, '', $varValue['name'], $tagAttribute); - - if (array_key_exists('children', $varValue)) { - self::climbtree($form, $varValue['children'], $elements); - } - } - return $elements; - } - /** * Set defaults for relevant form elements. * @@ -249,17 +194,7 @@ public static function setDefaults($id, &$defaults, $type = self::ALL, $fieldNam } if ($type & self::TAG) { - $fName = 'tag'; - if ($fieldName) { - $fName = $fieldName; - } - - $contactTag = CRM_Core_BAO_EntityTag::getTag($id); - if ($contactTag) { - foreach ($contactTag as $tag) { - $defaults[$fName . '[' . $tag . ']'] = 1; - } - } + $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag::getTag($id, 'civicrm_contact')); } } diff --git a/CRM/Contact/Form/Edit/Website.php b/CRM/Contact/Form/Edit/Website.php index 8cd7336eaec3..8930e9956c2d 100644 --- a/CRM/Contact/Form/Edit/Website.php +++ b/CRM/Contact/Form/Edit/Website.php @@ -1,9 +1,9 @@ applyFilter('__ALL__', 'trim'); //Website type select - $form->addField("website[$blockId][website_type_id]", array('entity' => 'website', 'class' => 'eight')); + $form->addField("website[$blockId][website_type_id]", ['entity' => 'website', 'class' => 'eight', 'placeholder' => NULL]); //Website box - $form->addField("website[$blockId][url]", array('entity' => 'website')); + $form->addField("website[$blockId][url]", ['entity' => 'website', 'aria-label' => ts('Website URL %1', [1 => $blockId])]); $form->addRule("website[$blockId][url]", ts('Enter a valid web address beginning with \'http://\' or \'https://\'.'), 'url'); } diff --git a/CRM/Contact/Form/GroupContact.php b/CRM/Contact/Form/GroupContact.php index 27377e2c8e30..da68e58b7755 100644 --- a/CRM/Contact/Form/GroupContact.php +++ b/CRM/Contact/Form/GroupContact.php @@ -1,9 +1,9 @@ _contactId = $this->get('contactId'); $this->_groupContactId = $this->get('groupContactId'); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); } /** @@ -80,7 +80,17 @@ public function buildQuickForm() { // get the list of all the groups if ($this->_context == 'user') { $onlyPublicGroups = CRM_Utils_Request::retrieve('onlyPublicGroups', 'Boolean', $this, FALSE); - $allGroups = CRM_Core_PseudoConstant::staticGroup($onlyPublicGroups); + $ids = CRM_Core_PseudoConstant::allGroup(); + $heirGroups = CRM_Contact_BAO_Group::getGroupsHierarchy($ids); + + $allGroups = []; + foreach ($heirGroups as $id => $group) { + // make sure that this group has public visibility + if ($onlyPublicGroups && $group['visibility'] == 'User and User Admin Only') { + continue; + } + $allGroups[$id] = $group; + } } else { $allGroups = CRM_Core_PseudoConstant::group(); @@ -101,7 +111,7 @@ public function buildQuickForm() { $groupSelect = $groupHierarchy; } - $groupSelect = array('' => ts('- select group -')) + $groupSelect; + $groupSelect = ['' => ts('- select group -')] + $groupSelect; if (count($groupSelect) > 1) { $session = CRM_Core_Session::singleton(); @@ -113,16 +123,15 @@ public function buildQuickForm() { $msg = ts('Add to a group'); } - $this->addField('group_id', array('class' => 'crm-action-menu fa-plus', 'placeholder' => $msg, 'options' => $groupSelect)); + $this->addField('group_id', ['class' => 'crm-action-menu fa-plus', 'placeholder' => $msg, 'options' => $groupSelect]); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Add'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Add'), + 'isDefault' => TRUE, + ], + ]); } } @@ -130,7 +139,7 @@ public function buildQuickForm() { * Post process form. */ public function postProcess() { - $contactID = array($this->_contactId); + $contactID = [$this->_contactId]; $groupId = $this->controller->exportValue('GroupContact', 'group_id'); $method = ($this->_context == 'user') ? 'Web' : 'Admin'; @@ -144,7 +153,7 @@ public function postProcess() { if ($groupContact && $this->_context != 'user') { $groups = CRM_Core_PseudoConstant::group(); - CRM_Core_Session::setStatus(ts("Contact has been added to '%1'.", array(1 => $groups[$groupId])), ts('Added to Group'), 'success'); + CRM_Core_Session::setStatus(ts("Contact has been added to '%1'.", [1 => $groups[$groupId]]), ts('Added to Group'), 'success'); } } diff --git a/CRM/Contact/Form/Inline.php b/CRM/Contact/Form/Inline.php index 01340a47bd69..0a03b0475e34 100644 --- a/CRM/Contact/Form/Inline.php +++ b/CRM/Contact/Form/Inline.php @@ -1,9 +1,9 @@ _contactId); - $buttons = array( - array( + $buttons = [ + [ 'type' => 'upload', 'name' => ts('Save'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - ); + ], + ]; $this->addButtons($buttons); } @@ -114,7 +117,7 @@ public function buildQuickForm() { * Override default cancel action. */ public function cancelAction() { - $response = array('status' => 'cancel'); + $response = ['status' => 'cancel']; CRM_Utils_JSON::output($response); } @@ -124,7 +127,7 @@ public function cancelAction() { * @return array */ public function setDefaultValues() { - $defaults = $params = array(); + $defaults = $params = []; $params['id'] = $this->_contactId; CRM_Contact_BAO_Contact::getValues($params, $defaults); @@ -175,11 +178,11 @@ public static function renderFooter($cid, $includeCount = TRUE) { 'contact_view_options', TRUE ); $smarty->assign('changeLog', $viewOptions['log']); - $ret = array('markup' => $smarty->fetch('CRM/common/contactFooter.tpl')); + $ret = ['markup' => $smarty->fetch('CRM/common/contactFooter.tpl')]; if ($includeCount) { $ret['count'] = CRM_Contact_BAO_Contact::getCountComponent('log', $cid); } - return array('changeLog' => $ret); + return ['changeLog' => $ret]; } } diff --git a/CRM/Contact/Form/Inline/Address.php b/CRM/Contact/Form/Inline/Address.php index e6c7e3504144..eda850c7d3ec 100644 --- a/CRM/Contact/Form/Inline/Address.php +++ b/CRM/Contact/Form/Inline/Address.php @@ -1,9 +1,9 @@ assign('addressSequence', $addressSequence); - $this->_values = array(); + $this->_values = []; $this->_addressId = CRM_Utils_Request::retrieve('aid', 'Positive', $this, FALSE, NULL, $_REQUEST); $this->_action = CRM_Core_Action::ADD; if ($this->_addressId) { - $params = array('id' => $this->_addressId); + $params = ['id' => $this->_addressId]; $address = CRM_Core_BAO_Address::getValues($params, FALSE, 'id'); $this->_values['address'][$this->_locBlockNo] = array_pop($address); $this->_action = CRM_Core_Action::UPDATE; @@ -125,7 +130,7 @@ public function preProcess() { public function buildQuickForm() { parent::buildQuickForm(); CRM_Contact_Form_Edit_Address::buildQuickForm($this, $this->_locBlockNo, TRUE, TRUE); - $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Edit_Address', 'formRule'], $this); } /** diff --git a/CRM/Contact/Form/Inline/CommunicationPreferences.php b/CRM/Contact/Form/Inline/CommunicationPreferences.php index dc9d321ea0dc..adbc82f07924 100644 --- a/CRM/Contact/Form/Inline/CommunicationPreferences.php +++ b/CRM/Contact/Form/Inline/CommunicationPreferences.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'], $this); } /** @@ -98,6 +98,9 @@ public function postProcess() { $params['contact_sub_type'] = $this->_contactSubType; } + if (!isset($params['preferred_communication_method'])) { + $params['preferred_communication_method'] = 'null'; + } CRM_Contact_BAO_Contact::create($params); $this->response(); diff --git a/CRM/Contact/Form/Inline/ContactInfo.php b/CRM/Contact/Form/Inline/ContactInfo.php index 48ef0d31b43a..55f1a1ddc822 100644 --- a/CRM/Contact/Form/Inline/ContactInfo.php +++ b/CRM/Contact/Form/Inline/ContactInfo.php @@ -1,9 +1,9 @@ ajaxResponse['updateTabs'] = array( + $this->ajaxResponse['updateTabs'] = [ '#tab_rel' => CRM_Contact_BAO_Contact::getCountComponent('rel', $this->_contactId), - ); + ]; if (CRM_Core_Permission::access('CiviContribute')) { $this->ajaxResponse['updateTabs']['#tab_contribute'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); } diff --git a/CRM/Contact/Form/Inline/ContactName.php b/CRM/Contact/Form/Inline/ContactName.php index 1fabfabdd535..2458683ff26f 100644 --- a/CRM/Contact/Form/Inline/ContactName.php +++ b/CRM/Contact/Form/Inline/ContactName.php @@ -1,9 +1,9 @@ _contactType; $class::buildQuickForm($this, 1); - $this->addFormRule(array('CRM_Contact_Form_Inline_ContactName', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Inline_ContactName', 'formRule'], $this); } /** @@ -63,7 +63,7 @@ public static function formRule($fields, $errors, $form) { if (empty($fields['first_name']) && empty($fields['last_name']) && empty($fields['organization_name']) && empty($fields['household_name'])) { - $emails = civicrm_api3('Email', 'getcount', array('contact_id' => $form->_contactId)); + $emails = civicrm_api3('Email', 'getcount', ['contact_id' => $form->_contactId]); if (!$emails) { $errorField = $form->_contactType == 'Individual' ? 'last' : strtolower($form->_contactType); $errors[$errorField . '_name'] = ts('Contact with no email must have a name.'); diff --git a/CRM/Contact/Form/Inline/CustomData.php b/CRM/Contact/Form/Inline/CustomData.php index 418969f183a1..046ff6e4cb6b 100644 --- a/CRM/Contact/Form/Inline/CustomData.php +++ b/CRM/Contact/Form/Inline/CustomData.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Inline_Email', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Inline_Email', 'formRule'], $this); } /** @@ -119,7 +121,7 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $errors, $form) { - $hasData = $hasPrimary = $errors = array(); + $hasData = $hasPrimary = $errors = []; if (!empty($fields['email']) && is_array($fields['email'])) { foreach ($fields['email'] as $instance => $blockValues) { $dataExists = CRM_Contact_Form_Contact::blockDataExists($blockValues); @@ -152,7 +154,7 @@ public static function formRule($fields, $errors, $form) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!empty($this->_emails)) { foreach ($this->_emails as $id => $value) { $defaults['email'][$id] = $value; @@ -191,7 +193,7 @@ public function postProcess() { if ($email['is_primary']) { CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'display_name', $email['email']); CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'sort_name', $email['email']); - $this->ajaxResponse['reloadBlocks'] = array('#crm-contactname-content'); + $this->ajaxResponse['reloadBlocks'] = ['#crm-contactname-content']; break; } } diff --git a/CRM/Contact/Form/Inline/IM.php b/CRM/Contact/Form/Inline/IM.php index 1464c320aed1..89658d42bced 100644 --- a/CRM/Contact/Form/Inline/IM.php +++ b/CRM/Contact/Form/Inline/IM.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Inline_IM', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Inline_IM', 'formRule']); } /** @@ -102,7 +104,7 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $errors) { - $hasData = $hasPrimary = $errors = array(); + $hasData = $hasPrimary = $errors = []; if (!empty($fields['im']) && is_array($fields['im'])) { foreach ($fields['im'] as $instance => $blockValues) { $dataExists = CRM_Contact_Form_Contact::blockDataExists($blockValues); @@ -135,7 +137,7 @@ public static function formRule($fields, $errors) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!empty($this->_ims)) { foreach ($this->_ims as $id => $value) { $defaults['im'][$id] = $value; diff --git a/CRM/Contact/Form/Inline/Lock.php b/CRM/Contact/Form/Inline/Lock.php index be6d6b024d1f..62c94420f5b0 100644 --- a/CRM/Contact/Form/Inline/Lock.php +++ b/CRM/Contact/Form/Inline/Lock.php @@ -1,9 +1,9 @@ addElement('hidden', 'oplock_ts', $timestamps['modified_date'], array('id' => 'oplock_ts')); - $form->addFormRule(array('CRM_Contact_Form_Inline_Lock', 'formRule'), $contactID); + $form->addElement('hidden', 'oplock_ts', $timestamps['modified_date'], ['id' => 'oplock_ts']); + $form->addFormRule(['CRM_Contact_Form_Inline_Lock', 'formRule'], $contactID); } /** @@ -70,7 +70,7 @@ public static function buildQuickForm(&$form, $contactID) { * true if no errors, else array of errors */ public static function formRule($fields, $files, $contactID = NULL) { - $errors = array(); + $errors = []; $timestamps = CRM_Contact_BAO_Contact::getTimestamps($contactID); if ($fields['oplock_ts'] != $timestamps['modified_date']) { @@ -93,7 +93,7 @@ public static function formRule($fields, $files, $contactID = NULL) { */ public static function getResponse($contactID) { $timestamps = CRM_Contact_BAO_Contact::getTimestamps($contactID); - return array('oplock_ts' => $timestamps['modified_date']); + return ['oplock_ts' => $timestamps['modified_date']]; } } diff --git a/CRM/Contact/Form/Inline/OpenID.php b/CRM/Contact/Form/Inline/OpenID.php index 6d102887a492..399279fd98e1 100644 --- a/CRM/Contact/Form/Inline/OpenID.php +++ b/CRM/Contact/Form/Inline/OpenID.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Inline_OpenID', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Inline_OpenID', 'formRule']); } /** @@ -102,7 +104,7 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $errors) { - $hasData = $hasPrimary = $errors = array(); + $hasData = $hasPrimary = $errors = []; if (!empty($fields['openid']) && is_array($fields['openid'])) { foreach ($fields['openid'] as $instance => $blockValues) { $dataExists = CRM_Contact_Form_Contact::blockDataExists($blockValues); @@ -135,7 +137,7 @@ public static function formRule($fields, $errors) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!empty($this->_openids)) { foreach ($this->_openids as $id => $value) { $defaults['openid'][$id] = $value; diff --git a/CRM/Contact/Form/Inline/Phone.php b/CRM/Contact/Form/Inline/Phone.php index 26872ac827b6..8cd7b6f729b1 100644 --- a/CRM/Contact/Form/Inline/Phone.php +++ b/CRM/Contact/Form/Inline/Phone.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Inline_Phone', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Inline_Phone', 'formRule']); } /** @@ -102,7 +104,7 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $errors) { - $hasData = $hasPrimary = $errors = array(); + $hasData = $hasPrimary = $errors = []; if (!empty($fields['phone']) && is_array($fields['phone'])) { $primaryID = NULL; foreach ($fields['phone'] as $instance => $blockValues) { @@ -136,7 +138,7 @@ public static function formRule($fields, $errors) { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!empty($this->_phones)) { foreach ($this->_phones as $id => $value) { $defaults['phone'][$id] = $value; diff --git a/CRM/Contact/Form/Inline/Website.php b/CRM/Contact/Form/Inline/Website.php index 7671a5fd3bad..a90ae37734a7 100644 --- a/CRM/Contact/Form/Inline/Website.php +++ b/CRM/Contact/Form/Inline/Website.php @@ -1,9 +1,9 @@ $this->_contactId); - $values = array(); + $params = ['contact_id' => $this->_contactId]; + $values = []; $this->_websites = CRM_Core_BAO_Website::getValues($params, $values); } @@ -87,6 +89,8 @@ public function buildQuickForm() { CRM_Contact_Form_Edit_Website::buildQuickForm($this, $blockId, TRUE); } + $this->addFormRule(['CRM_Contact_Form_Inline_Website', 'formRule'], $this); + } /** @@ -95,7 +99,7 @@ public function buildQuickForm() { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (!empty($this->_websites)) { foreach ($this->_websites as $id => $value) { $defaults['website'][$id] = $value; @@ -122,10 +126,44 @@ public function postProcess() { } } // Process / save websites - CRM_Core_BAO_Website::create($params['website'], $this->_contactId, TRUE); + CRM_Core_BAO_Website::process($params['website'], $this->_contactId, TRUE); $this->log(); $this->response(); } + /** + * Global validation rules for the form. + * + * @param array $fields + * Posted values of the form. + * @param array $errors + * List of errors to be posted back to the form. + * @param CRM_Contact_Form_Inline_Website $form + * + * @return array + */ + public static function formRule($fields, $errors, $form) { + $hasData = $errors = []; + if (!empty($fields['website']) && is_array($fields['website'])) { + $types = []; + foreach ($fields['website'] as $instance => $blockValues) { + $dataExists = CRM_Contact_Form_Contact::blockDataExists($blockValues); + + if ($dataExists) { + $hasData[] = $instance; + if (!empty($blockValues['website_type_id'])) { + if (empty($types[$blockValues['website_type_id']])) { + $types[$blockValues['website_type_id']] = $blockValues['website_type_id']; + } + else { + $errors["website[" . $instance . "][website_type_id]"] = ts('Contacts may only have one website of each type at most.'); + } + } + } + } + } + return $errors; + } + } diff --git a/CRM/Contact/Form/Location.php b/CRM/Contact/Form/Location.php index eb3cdcbb294f..0f3bd5c63b77 100644 --- a/CRM/Contact/Form/Location.php +++ b/CRM/Contact/Form/Location.php @@ -1,9 +1,9 @@ _blocks = array( + $form->_blocks = [ 'Address' => ts('Address'), 'Email' => ts('Email'), 'Phone' => ts('Phone'), - ); + ]; } $form->assign('blocks', $form->_blocks); @@ -76,15 +76,14 @@ public static function preProcess(&$form) { */ public static function buildQuickForm(&$form) { // required for subsequent AJAX requests. - $ajaxRequestBlocks = array(); + $ajaxRequestBlocks = []; $generateAjaxRequest = 0; //build 1 instance of all blocks, without using ajax ... foreach ($form->_blocks as $blockName => $label) { - require_once str_replace('_', DIRECTORY_SEPARATOR, 'CRM_Contact_Form_Edit_' . $blockName) . '.php'; $name = strtolower($blockName); - $instances = array(1); + $instances = [1]; if (!empty($_POST[$name]) && is_array($_POST[$name])) { $instances = array_keys($_POST[$name]); } diff --git a/CRM/Contact/Form/Merge.php b/CRM/Contact/Form/Merge.php index 6381852a5792..da804021ea2c 100644 --- a/CRM/Contact/Form/Merge.php +++ b/CRM/Contact/Form/Merge.php @@ -1,9 +1,9 @@ _gid = $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE); $this->_mergeId = CRM_Utils_Request::retrieve('mergeId', 'Positive', $this, FALSE); $this->limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this, FALSE); - $urlParams = "reset=1&rgid={$this->_rgid}&gid={$this->_gid}&limit=" . $this->limit; + $this->criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $this, FALSE, '{}'); + + $urlParams = ['reset' => 1, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria]; $this->bounceIfInvalid($this->_cid, $this->_oid); - $this->_contactType = civicrm_api3('Contact', 'getvalue', array( - 'id' => $this->_cid, - 'return' => 'contact_type', - )); + $contacts = civicrm_api3('Contact', 'get', [ + 'id' => ['IN' => [$this->_cid, $this->_oid]], + 'return' => ['contact_type', 'modified_date', 'created_date', 'contact_sub_type'], + ])['values']; - $browseUrl = CRM_Utils_System::url('civicrm/contact/dedupefind', $urlParams . '&action=browse'); + $this->_contactType = $contacts[$this->_cid]['contact_type']; + + $browseUrl = CRM_Utils_System::url('civicrm/contact/dedupefind', array_merge($urlParams, ['action' => 'browse'])); if (!$this->_rgid) { // Unset browse URL as we have come from the search screen. $browseUrl = ''; - $this->_rgid = civicrm_api3('RuleGroup', 'getvalue', array( + $this->_rgid = civicrm_api3('RuleGroup', 'getvalue', [ 'contact_type' => $this->_contactType, 'used' => 'Supervised', 'return' => 'id', - )); + ]); } $this->assign('browseUrl', $browseUrl); if ($browseUrl) { CRM_Core_Session::singleton()->pushUserContext($browseUrl); } - $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $gid); + $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $gid, json_decode($this->criteria, TRUE), TRUE, $this->limit); $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); $where = "de.id IS NULL"; @@ -108,40 +123,38 @@ public function preProcess() { // get user info of main contact. $config = CRM_Core_Config::singleton(); - $config->doNotResetCache = 1; + CRM_Core_Config::setPermitCacheFlushMode(FALSE); $mainUfId = CRM_Core_BAO_UFMatch::getUFId($this->_cid); $mainUser = NULL; if ($mainUfId) { - // d6 compatible - if ($config->userSystem->is_drupal == '1') { - $mainUser = user_load($mainUfId); - } - elseif ($config->userFramework == 'Joomla') { - $mainUser = JFactory::getUser($mainUfId); - } - + $mainUser = $config->userSystem->getUser($this->_cid); $this->assign('mainUfId', $mainUfId); - $this->assign('mainUfName', $mainUser ? $mainUser->name : NULL); + $this->assign('mainUfName', $mainUser ? $mainUser['name'] : NULL); } - - $flipUrl = CRM_Utils_System::url('civicrm/contact/merge', - "reset=1&action=update&cid={$this->_oid}&oid={$this->_cid}&rgid={$this->_rgid}&gid={$gid}" - ); + $flipParams = array_merge($urlParams, ['action' => 'update', 'cid' => $this->_oid, 'oid' => $this->_cid]); if (!$flip) { - $flipUrl .= '&flip=1'; + $flipParams['flip'] = '1'; } + $flipUrl = CRM_Utils_System::url('civicrm/contact/merge', + $flipParams + ); $this->assign('flip', $flipUrl); $this->prev = $this->next = NULL; - foreach (array( - 'prev', - 'next', - ) as $position) { + foreach ([ + 'prev', + 'next', + ] as $position) { if (!empty($pos[$position])) { if ($pos[$position]['id1'] && $pos[$position]['id2']) { - $urlParams .= "&cid={$pos[$position]['id1']}&oid={$pos[$position]['id2']}&mergeId={$pos[$position]['mergeId']}&action=update"; - $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $urlParams); + $rowParams = array_merge($urlParams, [ + 'action' => 'update', + 'cid' => $pos[$position]['id1'], + 'oid' => $pos[$position]['id2'], + 'mergeId' => $pos[$position]['mergeId'], + ]); + $this->$position = CRM_Utils_System::url('civicrm/contact/merge', $rowParams); $this->assign($position, $this->$position); } } @@ -152,16 +165,9 @@ public function preProcess() { $otherUser = NULL; if ($otherUfId) { - // d6 compatible - if ($config->userSystem->is_drupal == '1') { - $otherUser = user_load($otherUfId); - } - elseif ($config->userFramework == 'Joomla') { - $otherUser = JFactory::getUser($otherUfId); - } - + $otherUser = $config->userSystem->getUser($this->_oid); $this->assign('otherUfId', $otherUfId); - $this->assign('otherUfName', $otherUser ? $otherUser->name : NULL); + $this->assign('otherUfName', $otherUser ? $otherUser['name'] : NULL); } $cmsUser = ($mainUfId && $otherUfId) ? TRUE : FALSE; @@ -177,11 +183,28 @@ public function preProcess() { $this->assign('main_cid', $main['contact_id']); $this->assign('other_cid', $other['contact_id']); $this->assign('rgid', $this->_rgid); + $this->assignSummaryRowsToTemplate($contacts); - $this->addElement('checkbox', 'toggleSelect', NULL, NULL, array('class' => 'select-rows')); + $this->addElement('checkbox', 'toggleSelect', NULL, NULL, ['class' => 'select-rows']); $this->assign('mainLocBlock', json_encode($rowsElementsAndInfo['main_details']['location_blocks'])); $this->assign('locationBlockInfo', json_encode(CRM_Dedupe_Merger::getLocationBlockInfo())); + $this->assign('mainContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_cid]['contact_sub_type'] ? $contacts[$this->_cid]['contact_sub_type'] : $contacts[$this->_cid]['contact_type'], + FALSE, + $this->_cid + )); + $this->assign('otherContactTypeIcon', CRM_Contact_BAO_Contact_Utils::getImage($contacts[$this->_oid]['contact_sub_type'] ? $contacts[$this->_oid]['contact_sub_type'] : $contacts[$this->_oid]['contact_type'], + FALSE, + $this->_oid + )); + + if (isset($rowsElementsAndInfo['rows']['move_contact_type'])) { + // We don't permit merging contacts of different types so this is just clutter - putting + // the icon next to the contact name is consistent with elsewhere and permits hover-info + // https://lab.civicrm.org/dev/core/issues/824 + unset($rowsElementsAndInfo['rows']['move_contact_type']); + } + $this->assign('rows', $rowsElementsAndInfo['rows']); // add elements @@ -191,16 +214,16 @@ public function preProcess() { // on the form. if (substr($element[1], 0, 13) === 'move_location') { $element[4] = array_merge( - (array) CRM_Utils_Array::value(4, $element, array()), - array( + (array) CRM_Utils_Array::value(4, $element, []), + [ 'data-location' => substr($element[1], 14), 'data-is_location' => TRUE, - )); + ]); } if (substr($element[1], 0, 15) === 'location_blocks') { // @todo We could add some data elements here to make jquery manipulation more straight-forward // @todo consider enabling if it is an add & defaulting to true. - $element[4] = array_merge((array) CRM_Utils_Array::value(4, $element, array()), array('disabled' => TRUE)); + $element[4] = array_merge((array) CRM_Utils_Array::value(4, $element, []), ['disabled' => TRUE]); } $this->addElement($element[0], $element[1], @@ -222,7 +245,7 @@ public function preProcess() { ->readUserContext()); } catch (CRM_Core_Exception $e) { - CRM_Core_Error::statusBounce(ts($e->getMessage())); + CRM_Core_Error::statusBounce($e->getMessage()); } } @@ -230,35 +253,36 @@ public function addRules() { } public function buildQuickForm() { - CRM_Utils_System::setTitle(ts('Merge %1 contacts', array(1 => $this->_contactType))); - $buttons = array(); + $this->unsavedChangesWarn = FALSE; + CRM_Utils_System::setTitle(ts('Merge %1 contacts', [1 => $this->_contactType])); + $buttons = []; - $buttons[] = array( + $buttons[] = [ 'type' => 'next', 'name' => $this->next ? ts('Merge and go to Next Pair') : ts('Merge'), 'isDefault' => TRUE, - 'icon' => $this->next ? 'circle-triangle-e' : 'check', - ); + 'icon' => $this->next ? 'fa-play-circle' : 'check', + ]; if ($this->next || $this->prev) { - $buttons[] = array( + $buttons[] = [ 'type' => 'submit', 'name' => ts('Merge and go to Listing'), - ); - $buttons[] = array( + ]; + $buttons[] = [ 'type' => 'done', 'name' => ts('Merge and View Result'), 'icon' => 'fa-check-circle', - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'cancel', 'name' => ts('Cancel'), - ); + ]; $this->addButtons($buttons); - $this->addFormRule(array('CRM_Contact_Form_Merge', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Merge', 'formRule'], $this); } /** @@ -269,13 +293,13 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; $link = CRM_Utils_System::href(ts('Flip between the original and duplicate contacts.'), 'civicrm/contact/merge', 'reset=1&action=update&cid=' . $self->_oid . '&oid=' . $self->_cid . '&rgid=' . $self->_rgid . '&flip=1' ); if (CRM_Contact_BAO_Contact::checkDomainContact($self->_oid)) { - $errors['_qf_default'] = ts("The Default Organization contact cannot be merged into another contact record. It is associated with the CiviCRM installation for this domain and contains information used for system functions. If you want to merge these records, you can: %1", array(1 => $link)); + $errors['_qf_default'] = ts("The Default Organization contact cannot be merged into another contact record. It is associated with the CiviCRM installation for this domain and contains information used for system functions. If you want to merge these records, you can: %1", [1 => $link]); } return $errors; } @@ -285,30 +309,29 @@ public function postProcess() { $formValues['main_details'] = $this->_mainDetails; $formValues['other_details'] = $this->_otherDetails; - $migrationData = array('migration_info' => $formValues); + $migrationData = ['migration_info' => $formValues]; CRM_Utils_Hook::merge('form', $migrationData, $this->_cid, $this->_oid); CRM_Dedupe_Merger::moveAllBelongings($this->_cid, $this->_oid, $migrationData['migration_info']); $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_cid, 'display_name'); - $message = '
    • ' . ts('%1 has been updated.', array(1 => $name)) . '
    • ' . ts('Contact ID %1 has been deleted.', array(1 => $this->_oid)) . '
    '; + $message = '
    • ' . ts('%1 has been updated.', [1 => $name]) . '
    • ' . ts('Contact ID %1 has been deleted.', [1 => $this->_oid]) . '
    '; CRM_Core_Session::setStatus($message, ts('Contacts Merged'), 'success'); - $url = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$this->_cid}"); - $urlParams = "reset=1&gid={$this->_gid}&rgid={$this->_rgid}&limit={$this->limit}"; + $urlParams = ['reset' => 1, 'cid' => $this->_cid, 'rgid' => $this->_rgid, 'gid' => $this->_gid, 'limit' => $this->limit, 'criteria' => $this->criteria]; + $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $this->_cid]); if (!empty($formValues['_qf_Merge_submit'])) { - $urlParams .= "&action=update"; - $lisitingURL = CRM_Utils_System::url('civicrm/contact/dedupefind', + $urlParams['action'] = "update"; + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlParams - ); - CRM_Utils_System::redirect($lisitingURL); + )); } if (!empty($formValues['_qf_Merge_done'])) { - CRM_Utils_System::redirect($url); + CRM_Utils_System::redirect($contactViewUrl); } if ($this->next && $this->_mergeId) { - $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid); + $cacheKey = CRM_Dedupe_Merger::getMergeCacheKeyString($this->_rgid, $this->_gid, json_decode($this->criteria, TRUE), TRUE, $this->limit); $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); $where = "de.id IS NULL"; @@ -320,12 +343,16 @@ public function postProcess() { $pos['next']['id2'] ) { - $urlParams .= "&cid={$pos['next']['id1']}&oid={$pos['next']['id2']}&mergeId={$pos['next']['mergeId']}&action=update"; - $url = CRM_Utils_System::url('civicrm/contact/merge', $urlParams); + $urlParams['cid'] = $pos['next']['id1']; + $urlParams['oid'] = $pos['next']['id2']; + $urlParams['mergeId'] = $pos['next']['mergeId']; + $urlParams['action'] = 'update'; + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/merge', $urlParams)); } } - CRM_Utils_System::redirect($url); + // Perhaps never reached. + CRM_Utils_System::redirect($contactViewUrl); } /** @@ -345,7 +372,7 @@ public function bounceIfInvalid($cid, $oid) { } if (!CRM_Dedupe_BAO_Rule::validateContacts($cid, $oid)) { - CRM_Core_Error::statusBounce(ts('The selected pair of contacts are marked as non duplicates. If these records should be merged, you can remove this exception on the Dedupe Exceptions page.', array(1 => CRM_Utils_System::url('civicrm/dedupe/exception', 'reset=1')))); + CRM_Core_Error::statusBounce(ts('The selected pair of contacts are marked as non duplicates. If these records should be merged, you can remove this exception on the Dedupe Exceptions page.', [1 => CRM_Utils_System::url('civicrm/dedupe/exception', 'reset=1')])); } if (!(CRM_Contact_BAO_Contact_Permission::allow($cid, CRM_Core_Permission::EDIT) && @@ -357,10 +384,40 @@ public function bounceIfInvalid($cid, $oid) { // ensure that oid is not the current user, if so refuse to do the merge if (CRM_Core_Session::singleton()->getLoggedInContactID() == $oid) { $message = ts('The contact record which is linked to the currently logged in user account - \'%1\' - cannot be deleted.', - array(1 => CRM_Core_Session::singleton()->getLoggedInContactDisplayName()) + [1 => CRM_Core_Session::singleton()->getLoggedInContactDisplayName()] ); CRM_Core_Error::statusBounce($message); } } + /** + * Assign the summary_rows variable to the tpl. + * + * This adds rows to the beginning of the block that will help in making merge choices. + * + * It can be modified by a hook by altering what is assigned. Although not technically supported this + * is an easy tweak with no earth-shattering impacts if later changes stop if from working. + * + * https://lab.civicrm.org/dev/core/issues/824 + * + * @param array $contacts + */ + protected function assignSummaryRowsToTemplate($contacts) { + $mostRecent = ($contacts[$this->_cid]['modified_date'] < $contacts[$this->_oid]['modified_date']) ? $this->_oid : $this->_cid; + $this->assign('summary_rows', [ + [ + 'name' => 'created_date', + 'label' => ts('Created'), + 'main_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_cid]['created_date']), + 'other_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_oid]['created_date']), + ], + [ + 'name' => 'modified_date', + 'label' => ts('Last Modified'), + 'main_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_cid]['modified_date']) . ($mostRecent == $this->_cid ? ' (' . ts('Most Recent') . ')' : ''), + 'other_contact_value' => CRM_Utils_Date::customFormat($contacts[$this->_oid]['modified_date']) . ($mostRecent == $this->_oid ? ' (' . ts('Most Recent') . ')' : ''), + ], + ]); + } + } diff --git a/CRM/Contact/Form/RelatedContact.php b/CRM/Contact/Form/RelatedContact.php index f2e1774868d5..2795870041ad 100644 --- a/CRM/Contact/Form/RelatedContact.php +++ b/CRM/Contact/Form/RelatedContact.php @@ -1,9 +1,9 @@ id = $this->_contactId; if (!$contact->find(TRUE)) { - CRM_Core_Error::statusBounce(ts('contact does not exist: %1', array(1 => $this->_contactId))); + CRM_Core_Error::statusBounce(ts('contact does not exist: %1', [1 => $this->_contactId])); } $this->_contactType = $contact->contact_type; @@ -104,7 +111,7 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - $params = array(); + $params = []; $params['id'] = $params['contact_id'] = $this->_contactId; $contact = CRM_Contact_BAO_Contact::retrieve($params, $this->_defaults); @@ -125,17 +132,17 @@ public function buildQuickForm() { ts('Contact Information') ); - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'next', 'name' => ts('Save'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); } /** @@ -146,11 +153,11 @@ public function postProcess() { $params = $this->controller->exportValues($this->_name); $locType = CRM_Core_BAO_LocationType::getDefault(); - foreach (array( - 'phone', - 'email', - 'address', - ) as $locFld) { + foreach ([ + 'phone', + 'email', + 'address', + ] as $locFld) { if (!empty($this->_defaults[$locFld]) && $this->_defaults[$locFld][1]['location_type_id']) { $params[$locFld][1]['is_primary'] = $this->_defaults[$locFld][1]['is_primary']; $params[$locFld][1]['location_type_id'] = $this->_defaults[$locFld][1]['location_type_id']; @@ -172,10 +179,10 @@ public function postProcess() { // set status message. if ($this->_contactId) { - $message = ts('%1 has been updated.', array(1 => $contact->display_name)); + $message = ts('%1 has been updated.', [1 => $contact->display_name]); } else { - $message = ts('%1 has been created.', array(1 => $contact->display_name)); + $message = ts('%1 has been created.', [1 => $contact->display_name]); } CRM_Core_Session::setStatus($message, ts('Contact Saved'), 'success'); } diff --git a/CRM/Contact/Form/Relationship.php b/CRM/Contact/Form/Relationship.php index e90727bb0715..eca04a56c6d0 100644 --- a/CRM/Contact/Form/Relationship.php +++ b/CRM/Contact/Form/Relationship.php @@ -1,9 +1,9 @@ assign('display_name_a', $this->_display_name_a); //get the relationship values. - $this->_values = array(); + $this->_values = []; if ($this->_relationshipId) { - $params = array('id' => $this->_relationshipId); + $params = ['id' => $this->_relationshipId]; CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Relationship', $params, $this->_values); } // Check for permissions - if (in_array($this->_action, array(CRM_Core_Action::ADD, CRM_Core_Action::UPDATE, CRM_Core_Action::DELETE))) { + if (in_array($this->_action, [CRM_Core_Action::ADD, CRM_Core_Action::UPDATE, CRM_Core_Action::DELETE])) { if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT) && !CRM_Contact_BAO_Contact_Permission::allow($this->_values['contact_id_b'], CRM_Core_Permission::EDIT)) { CRM_Core_Error::statusBounce(ts('You do not have the necessary permission to edit this contact.')); @@ -149,19 +155,19 @@ public function preProcess() { // Set page title based on action switch ($this->_action) { case CRM_Core_Action::VIEW: - CRM_Utils_System::setTitle(ts('View Relationship for %1', array(1 => $this->_display_name_a))); + CRM_Utils_System::setTitle(ts('View Relationship for %1', [1 => $this->_display_name_a])); break; case CRM_Core_Action::ADD: - CRM_Utils_System::setTitle(ts('Add Relationship for %1', array(1 => $this->_display_name_a))); + CRM_Utils_System::setTitle(ts('Add Relationship for %1', [1 => $this->_display_name_a])); break; case CRM_Core_Action::UPDATE: - CRM_Utils_System::setTitle(ts('Edit Relationship for %1', array(1 => $this->_display_name_a))); + CRM_Utils_System::setTitle(ts('Edit Relationship for %1', [1 => $this->_display_name_a])); break; case CRM_Core_Action::DELETE: - CRM_Utils_System::setTitle(ts('Delete Relationship for %1', array(1 => $this->_display_name_a))); + CRM_Utils_System::setTitle(ts('Delete Relationship for %1', [1 => $this->_display_name_a])); break; } @@ -178,7 +184,7 @@ public function preProcess() { } //get the relationship type id - $this->_relationshipTypeId = str_replace(array('_a_b', '_b_a'), array('', ''), $this->_rtypeId); + $this->_relationshipTypeId = str_replace(['_a_b', '_b_a'], ['', ''], $this->_rtypeId); //get the relationship type if (!$this->_rtype) { @@ -212,22 +218,17 @@ public function preProcess() { * Set default values for the form. */ public function setDefaultValues() { - - $defaults = array(); + $defaults = []; if ($this->_action & CRM_Core_Action::UPDATE) { if (!empty($this->_values)) { $defaults['relationship_type_id'] = $this->_rtypeId; - if (!empty($this->_values['start_date'])) { - list($defaults['start_date']) = CRM_Utils_Date::setDateDefaults($this->_values['start_date']); - } - if (!empty($this->_values['end_date'])) { - list($defaults['end_date']) = CRM_Utils_Date::setDateDefaults($this->_values['end_date']); - } + $defaults['start_date'] = CRM_Utils_Array::value('start_date', $this->_values); + $defaults['end_date'] = CRM_Utils_Array::value('end_date', $this->_values); $defaults['description'] = CRM_Utils_Array::value('description', $this->_values); $defaults['is_active'] = CRM_Utils_Array::value('is_active', $this->_values); - // The javascript on the form will swap these fields if it is a b_a relationship, so we compensate here + // The postprocess function will swap these fields if it is a b_a relationship, so we compensate here $defaults['is_permission_a_b'] = CRM_Utils_Array::value('is_permission_' . $this->_rtype, $this->_values); $defaults['is_permission_b_a'] = CRM_Utils_Array::value('is_permission_' . strrev($this->_rtype), $this->_values); @@ -247,12 +248,12 @@ public function setDefaultValues() { $this->assign('display_name_b', $this->_display_name_b); } - $noteParams = array( + $noteParams = [ 'entity_id' => $this->_relationshipId, 'entity_table' => 'civicrm_relationship', 'limit' => 1, 'version' => 3, - ); + ]; $note = civicrm_api('Note', 'getsingle', $noteParams); $defaults['note'] = CRM_Utils_Array::value('note', $note); } @@ -260,6 +261,7 @@ public function setDefaultValues() { else { $defaults['is_active'] = $defaults['is_current_employer'] = 1; $defaults['relationship_type_id'] = $this->_rtypeId; + $defaults['is_permission_a_b'] = $defaults['is_permission_b_a'] = CRM_Contact_BAO_Relationship::NONE; } $this->_enabled = $defaults['is_active']; @@ -270,9 +272,8 @@ public function setDefaultValues() { * Add the rules for form. */ public function addRules() { - if (!($this->_action & CRM_Core_Action::DELETE)) { - $this->addFormRule(array('CRM_Contact_Form_Relationship', 'dateRule')); + $this->addFormRule(['CRM_Contact_Form_Relationship', 'dateRule']); } } @@ -281,26 +282,24 @@ public function addRules() { */ public function buildQuickForm() { if ($this->_action & CRM_Core_Action::DELETE) { - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Delete'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Delete'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); return; } // Select list $relationshipList = CRM_Contact_BAO_Relationship::getContactRelationshipType($this->_contactId, $this->_rtype, $this->_relationshipId); - // Metadata needed on clientside - $this->assign('relationshipData', self::getRelationshipTypeMetadata($relationshipList)); + $this->assign('contactTypes', CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE)); foreach ($this->_allRelationshipNames as $id => $vals) { if ($vals['name_a_b'] === 'Employee of') { @@ -309,10 +308,25 @@ public function buildQuickForm() { } } - $this->addField('relationship_type_id', array('options' => array('' => ts('- select -')) + $relationshipList, 'class' => 'huge', 'placeholder' => '- select -'), TRUE); + $this->addField( + 'relationship_type_id', + [ + 'options' => ['' => ts('- select -')] + $relationshipList, + 'class' => 'huge', + 'placeholder' => '- select -', + 'option_url' => 'civicrm/admin/reltype', + 'option_context' => [ + 'contact_id' => $this->_contactId, + 'relationship_direction' => $this->_rtype, + 'relationship_id' => $this->_relationshipId, + 'is_form' => TRUE, + ], + ], + TRUE + ); $label = $this->_action & CRM_Core_Action::ADD ? ts('Contact(s)') : ts('Contact'); - $contactField = $this->addField('related_contact_id', array('label' => $label, 'name' => 'contact_id_b', 'multiple' => TRUE, 'create' => TRUE), TRUE); + $contactField = $this->addField('related_contact_id', ['label' => $label, 'name' => 'contact_id_b', 'multiple' => TRUE, 'create' => TRUE], TRUE); // This field cannot be updated if ($this->_action & CRM_Core_Action::UPDATE) { $contactField->freeze(); @@ -320,53 +334,55 @@ public function buildQuickForm() { $this->add('advcheckbox', 'is_current_employer', $this->_contactType == 'Organization' ? ts('Current Employee') : ts('Current Employer')); - $this->addField('start_date', array('label' => ts('Start Date'), 'formatType' => 'searchDate')); - $this->addField('end_date', array('label' => ts('End Date'), 'formatType' => 'searchDate')); + $this->addField('start_date', ['label' => ts('Start Date')], FALSE, FALSE); + $this->addField('end_date', ['label' => ts('End Date')], FALSE, FALSE); - $this->addField('is_active', array('label' => ts('Enabled?'), 'type' => 'advcheckbox')); + $this->addField('is_active', ['label' => ts('Enabled?'), 'type' => 'advcheckbox']); - $this->addField('is_permission_a_b'); - $this->addField('is_permission_b_a'); + $this->addField('is_permission_a_b', [], TRUE); + $this->addField('is_permission_b_a', [], TRUE); - $this->addField('description', array('label' => ts('Description'))); + $this->addField('description', ['label' => ts('Description')]); CRM_Contact_Form_Edit_Notes::buildQuickForm($this); if ($this->_action & CRM_Core_Action::VIEW) { - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'cancel', 'name' => ts('Done'), - ), - )); + ], + ]); } else { // make this form an upload since we don't know if the custom data injected dynamically is of type file etc. - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'upload', 'name' => ts('Save Relationship'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - )); + ], + ]); } } /** * This function is called when the form is submitted and also from unit test. + * * @param array $params * * @return array + * @throws \CRM_Core_Exception */ public function submit($params) { switch ($this->getAction()) { case CRM_Core_Action::DELETE: $this->deleteAction($this->_relationshipId); - return array(); + return []; case CRM_Core_Action::UPDATE: return $this->updateAction($params); @@ -400,13 +416,14 @@ public function postProcess() { $note = !empty($params['note']) ? $params['note'] : ''; $this->saveRelationshipNotes($relationshipIds, $note); - $this->setEmploymentRelationship($params, $relationshipIds); - // Refresh contact tabs which might have been affected - $this->ajaxResponse['updateTabs'] = array( - '#tab_member' => CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactId), - '#tab_contribute' => CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId), - ); + $this->ajaxResponse = [ + 'reloadBlocks' => ['#crm-contactinfo-content'], + 'updateTabs' => [ + '#tab_member' => CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactId), + '#tab_contribute' => CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId), + ], + ]; } /** @@ -419,13 +436,11 @@ public function postProcess() { * mixed true or array of errors */ public static function dateRule($params) { - $errors = array(); + $errors = []; // check start and end date if (!empty($params['start_date']) && !empty($params['end_date'])) { - $start_date = CRM_Utils_Date::format(CRM_Utils_Array::value('start_date', $params)); - $end_date = CRM_Utils_Date::format(CRM_Utils_Array::value('end_date', $params)); - if ($start_date && $end_date && (int ) $end_date < (int ) $start_date) { + if ($params['end_date'] < $params['start_date']) { $errors['end_date'] = ts('The relationship end date cannot be prior to the start date.'); } } @@ -445,28 +460,28 @@ public static function dateRule($params) { */ protected function setMessage($outcome) { if (!empty($outcome['valid']) && empty($outcome['saved'])) { - CRM_Core_Session::setStatus(ts('Relationship created.', array( + CRM_Core_Session::setStatus(ts('Relationship created.', [ 'count' => $outcome['valid'], 'plural' => '%count relationships created.', - )), ts('Saved'), 'success'); + ]), ts('Saved'), 'success'); } if (!empty($outcome['invalid'])) { - CRM_Core_Session::setStatus(ts('%count relationship record was not created due to an invalid contact type.', array( + CRM_Core_Session::setStatus(ts('%count relationship record was not created due to an invalid contact type.', [ 'count' => $outcome['invalid'], 'plural' => '%count relationship records were not created due to invalid contact types.', - )), ts('%count invalid relationship record', array( + ]), ts('%count invalid relationship record', [ 'count' => $outcome['invalid'], 'plural' => '%count invalid relationship records', - ))); + ])); } if (!empty($outcome['duplicate'])) { - CRM_Core_Session::setStatus(ts('One relationship was not created because it already exists.', array( + CRM_Core_Session::setStatus(ts('One relationship was not created because it already exists.', [ 'count' => $outcome['duplicate'], 'plural' => '%count relationships were not created because they already exist.', - )), ts('%count duplicate relationship', array( + ]), ts('%count duplicate relationship', [ 'count' => $outcome['duplicate'], 'plural' => '%count duplicate relationships', - ))); + ])); } if (!empty($outcome['saved'])) { CRM_Core_Session::setStatus(ts('Relationship record has been updated.'), ts('Saved'), 'success'); @@ -475,26 +490,27 @@ protected function setMessage($outcome) { /** * @param $relationshipList + * * @return array */ public static function getRelationshipTypeMetadata($relationshipList) { $contactTypes = CRM_Contact_BAO_ContactType::contactTypeInfo(TRUE); $allRelationshipNames = CRM_Core_PseudoConstant::relationshipType('name'); - $jsData = array(); + $jsData = []; // Get just what we need to keep the dom small - $whatWeWant = array_flip(array( + $whatWeWant = array_flip([ 'contact_type_a', 'contact_type_b', 'contact_sub_type_a', 'contact_sub_type_b', - )); + ]); foreach ($allRelationshipNames as $id => $vals) { if (isset($relationshipList["{$id}_a_b"]) || isset($relationshipList["{$id}_b_a"])) { $jsData[$id] = array_filter(array_intersect_key($allRelationshipNames[$id], $whatWeWant)); // Add user-friendly placeholder - foreach (array('a', 'b') as $x) { + foreach (['a', 'b'] as $x) { $type = !empty($jsData[$id]["contact_sub_type_$x"]) ? $jsData[$id]["contact_sub_type_$x"] : CRM_Utils_Array::value("contact_type_$x", $jsData[$id]); - $jsData[$id]["placeholder_$x"] = $type ? ts('- select %1 -', array(strtolower($contactTypes[$type]['label']))) : ts('- select contact -'); + $jsData[$id]["placeholder_$x"] = $type ? ts('- select %1 -', [strtolower($contactTypes[$type]['label'])]) : ts('- select contact -'); } } } @@ -511,7 +527,7 @@ private function deleteAction($id) { CRM_Contact_BAO_Relationship::del($id); // reload all blocks to reflect this change on the user interface. - $this->ajaxResponse['reloadBlocks'] = array('#crm-contactinfo-content'); + $this->ajaxResponse['reloadBlocks'] = ['#crm-contactinfo-content']; } /** @@ -520,11 +536,10 @@ private function deleteAction($id) { * @param array $params * * @return array + * @throws \CRM_Core_Exception */ private function updateAction($params) { - $params = $this->preparePostProcessParameters($params); - $params = $params[0]; - + list($params, $_) = $this->preparePostProcessParameters($params); try { civicrm_api3('relationship', 'create', $params); } @@ -532,11 +547,8 @@ private function updateAction($params) { throw new CRM_Core_Exception('Relationship create error ' . $e->getMessage()); } - $this->clearCurrentEmployer($params); - - $this->setMessage(array('saved' => TRUE)); - - return array($params, array($this->_relationshipId)); + $this->setMessage(['saved' => TRUE]); + return [$params, [$this->_relationshipId]]; } /** @@ -545,6 +557,7 @@ private function updateAction($params) { * @param array $params * * @return array + * @throws \CRM_Core_Exception */ private function createAction($params) { list($params, $primaryContactLetter) = $this->preparePostProcessParameters($params); @@ -555,42 +568,36 @@ private function createAction($params) { $this->setMessage($outcome); - return array($params, $relationshipIds); + return [$params, $relationshipIds]; } - /** * Prepares parameters to be used for create/update actions * - * @param array $params + * @param array $values * * @return array */ - private function preparePostProcessParameters($params) { - $relationshipTypeParts = explode('_', $params['relationship_type_id']); + private function preparePostProcessParameters($values) { + $params = $values; + list($relationshipTypeId, $a, $b) = explode('_', $params['relationship_type_id']); - $params['relationship_type_id'] = $relationshipTypeParts[0]; - $params['contact_id_' . $relationshipTypeParts[1]] = $this->_contactId; + $params['relationship_type_id'] = $relationshipTypeId; + $params['contact_id_' . $a] = $this->_contactId; if (empty($this->_relationshipId)) { - $params['contact_id_' . $relationshipTypeParts[2]] = explode(',', $params['related_contact_id']); + $params['contact_id_' . $b] = explode(',', $params['related_contact_id']); } else { $params['id'] = $this->_relationshipId; - $params['contact_id_' . $relationshipTypeParts[2]] = $params['related_contact_id']; - - foreach (array('start_date', 'end_date') as $dateParam) { - if (!empty($params[$dateParam])) { - $params[$dateParam] = CRM_Utils_Date::processDate($params[$dateParam]); - } - } + $params['contact_id_' . $b] = $params['related_contact_id']; } - // CRM-14612 - Don't use adv-checkbox as it interferes with the form js - $params['is_permission_a_b'] = CRM_Utils_Array::value('is_permission_a_b', $params, 0); - $params['is_permission_b_a'] = CRM_Utils_Array::value('is_permission_b_a', $params, 0); + // If this is a b_a relationship these form elements are flipped + $params['is_permission_a_b'] = CRM_Utils_Array::value("is_permission_{$a}_{$b}", $values, 0); + $params['is_permission_b_a'] = CRM_Utils_Array::value("is_permission_{$b}_{$a}", $values, 0); - return array($params, $relationshipTypeParts[1]); + return [$params, $a]; } /** @@ -598,13 +605,15 @@ private function preparePostProcessParameters($params) { * * @param array $relationshipIds * @param string $note + * + * @throws \CiviCRM_API3_Exception */ private function saveRelationshipNotes($relationshipIds, $note) { foreach ($relationshipIds as $id) { - $noteParams = array( + $noteParams = [ 'entity_id' => $id, 'entity_table' => 'civicrm_relationship', - ); + ]; $existing = civicrm_api3('note', 'get', $noteParams); if (!empty($existing['id'])) { @@ -627,46 +636,4 @@ private function saveRelationshipNotes($relationshipIds, $note) { } } - /** - * Sets current employee/employer relationship - * - * @param $params - * @param array $relationshipIds - */ - private function setEmploymentRelationship($params, $relationshipIds) { - if ( - !empty($params['is_current_employer']) && - $this->_allRelationshipNames[$params['relationship_type_id']]["name_a_b"] == 'Employee of') { - $employerParams = array(); - foreach ($relationshipIds as $id) { - // Fixme this is dumb why do we have to look this up again? - $rel = CRM_Contact_BAO_Relationship::getRelationshipByID($id); - $employerParams[$rel->contact_id_a] = $rel->contact_id_b; - } - // @todo this belongs in the BAO. - CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($employerParams); - // Refresh contact summary if in ajax mode - $this->ajaxResponse['reloadBlocks'] = array('#crm-contactinfo-content'); - } - } - - /** - * Clears the current employer if the relationship type - * get changed, disabled or 'current employer' checkbox get unchecked. - * - * @param $params - */ - private function clearCurrentEmployer($params) { - // @todo this belongs in the BAO. - if ($this->_isCurrentEmployer) { - $relChanged = $params['relationship_type_id'] != $this->_values['relationship_type_id']; - if (!$params['is_active'] || !$params['is_current_employer'] || $relChanged) { - CRM_Contact_BAO_Contact_Utils::clearCurrentEmployer($this->_values['contact_id_a']); - - // Refresh contact summary if in ajax mode - $this->ajaxResponse['reloadBlocks'] = array('#crm-contactinfo-content'); - } - } - } - } diff --git a/CRM/Contact/Form/Search.php b/CRM/Contact/Form/Search.php index 08a5019fd953..96fb7810d070 100644 --- a/CRM/Contact/Form/Search.php +++ b/CRM/Contact/Form/Search.php @@ -1,9 +1,9 @@ 'Show members of group', 'amtg' => 'Add members to group', 'basic' => 'Basic Search', @@ -183,7 +187,7 @@ public static function &validContext() { 'builder' => 'Search Builder', 'advanced' => 'Advanced Search', 'custom' => 'Custom Search', - ); + ]; } return self::$_validContext; } @@ -199,124 +203,170 @@ public static function isSearchContext($context) { } public static function setModeValues() { - if (!self::$_modeValues) { - self::$_modeValues = array( - 1 => array( - 'selectorName' => self::$_selectorName, - 'selectorLabel' => ts('Contacts'), - 'taskFile' => 'CRM/Contact/Form/Search/ResultTasks.tpl', - 'taskContext' => NULL, - 'resultFile' => 'CRM/Contact/Form/Selector.tpl', - 'resultContext' => NULL, - 'taskClassName' => 'CRM_Contact_Task', - ), - 2 => array( - 'selectorName' => 'CRM_Contribute_Selector_Search', - 'selectorLabel' => ts('Contributions'), - 'taskFile' => 'CRM/common/searchResultTasks.tpl', - 'taskContext' => 'Contribution', - 'resultFile' => 'CRM/Contribute/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Contribute_Task', - ), - 3 => array( - 'selectorName' => 'CRM_Event_Selector_Search', - 'selectorLabel' => ts('Event Participants'), - 'taskFile' => 'CRM/common/searchResultTasks.tpl', - 'taskContext' => NULL, - 'resultFile' => 'CRM/Event/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Event_Task', - ), - 4 => array( - 'selectorName' => 'CRM_Activity_Selector_Search', - 'selectorLabel' => ts('Activities'), - 'taskFile' => 'CRM/common/searchResultTasks.tpl', - 'taskContext' => NULL, - 'resultFile' => 'CRM/Activity/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Activity_Task', - ), - 5 => array( - 'selectorName' => 'CRM_Member_Selector_Search', - 'selectorLabel' => ts('Memberships'), - 'taskFile' => "CRM/common/searchResultTasks.tpl", - 'taskContext' => NULL, - 'resultFile' => 'CRM/Member/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Member_Task', - ), - 6 => array( - 'selectorName' => 'CRM_Case_Selector_Search', - 'selectorLabel' => ts('Cases'), - 'taskFile' => "CRM/common/searchResultTasks.tpl", - 'taskContext' => NULL, - 'resultFile' => 'CRM/Case/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Case_Task', - ), - 7 => array( - 'selectorName' => self::$_selectorName, - 'selectorLabel' => ts('Related Contacts'), - 'taskFile' => 'CRM/Contact/Form/Search/ResultTasks.tpl', - 'taskContext' => NULL, - 'resultFile' => 'CRM/Contact/Form/Selector.tpl', - 'resultContext' => NULL, - 'taskClassName' => 'CRM_Contact_Task', - ), - 8 => array( - 'selectorName' => 'CRM_Mailing_Selector_Search', - 'selectorLabel' => ts('Mailings'), - 'taskFile' => "CRM/common/searchResultTasks.tpl", - 'taskContext' => NULL, - 'resultFile' => 'CRM/Mailing/Form/Selector.tpl', - 'resultContext' => 'Search', - 'taskClassName' => 'CRM_Mailing_Task', - ), - ); - } + self::$_modeValues = [ + CRM_Contact_BAO_Query::MODE_CONTACTS => [ + 'selectorName' => self::$_selectorName, + 'selectorLabel' => ts('Contacts'), + 'taskFile' => 'CRM/Contact/Form/Search/ResultTasks.tpl', + 'taskContext' => NULL, + 'resultFile' => 'CRM/Contact/Form/Selector.tpl', + 'resultContext' => NULL, + 'taskClassName' => 'CRM_Contact_Task', + 'component' => '', + ], + CRM_Contact_BAO_Query::MODE_CONTRIBUTE => [ + 'selectorName' => 'CRM_Contribute_Selector_Search', + 'selectorLabel' => ts('Contributions'), + 'taskFile' => 'CRM/common/searchResultTasks.tpl', + 'taskContext' => 'Contribution', + 'resultFile' => 'CRM/Contribute/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Contribute_Task', + 'component' => 'CiviContribute', + ], + CRM_Contact_BAO_Query::MODE_EVENT => [ + 'selectorName' => 'CRM_Event_Selector_Search', + 'selectorLabel' => ts('Event Participants'), + 'taskFile' => 'CRM/common/searchResultTasks.tpl', + 'taskContext' => NULL, + 'resultFile' => 'CRM/Event/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Event_Task', + 'component' => 'CiviEvent', + ], + CRM_Contact_BAO_Query::MODE_ACTIVITY => [ + 'selectorName' => 'CRM_Activity_Selector_Search', + 'selectorLabel' => ts('Activities'), + 'taskFile' => 'CRM/common/searchResultTasks.tpl', + 'taskContext' => NULL, + 'resultFile' => 'CRM/Activity/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Activity_Task', + 'component' => 'activity', + ], + CRM_Contact_BAO_Query::MODE_MEMBER => [ + 'selectorName' => 'CRM_Member_Selector_Search', + 'selectorLabel' => ts('Memberships'), + 'taskFile' => "CRM/common/searchResultTasks.tpl", + 'taskContext' => NULL, + 'resultFile' => 'CRM/Member/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Member_Task', + 'component' => 'CiviMember', + ], + CRM_Contact_BAO_Query::MODE_CASE => [ + 'selectorName' => 'CRM_Case_Selector_Search', + 'selectorLabel' => ts('Cases'), + 'taskFile' => "CRM/common/searchResultTasks.tpl", + 'taskContext' => NULL, + 'resultFile' => 'CRM/Case/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Case_Task', + 'component' => 'CiviCase', + ], + CRM_Contact_BAO_Query::MODE_CONTACTSRELATED => [ + 'selectorName' => self::$_selectorName, + 'selectorLabel' => ts('Related Contacts'), + 'taskFile' => 'CRM/Contact/Form/Search/ResultTasks.tpl', + 'taskContext' => NULL, + 'resultFile' => 'CRM/Contact/Form/Selector.tpl', + 'resultContext' => NULL, + 'taskClassName' => 'CRM_Contact_Task', + 'component' => 'related_contact', + ], + CRM_Contact_BAO_Query::MODE_MAILING => [ + 'selectorName' => 'CRM_Mailing_Selector_Search', + 'selectorLabel' => ts('Mailings'), + 'taskFile' => "CRM/common/searchResultTasks.tpl", + 'taskContext' => NULL, + 'resultFile' => 'CRM/Mailing/Form/Selector.tpl', + 'resultContext' => 'Search', + 'taskClassName' => 'CRM_Mailing_Task', + 'component' => 'CiviMail', + ], + ]; } /** + * Get the metadata for the query mode (this includes task class names) + * * @param int $mode * - * @return mixed + * @return array + * @throws \CRM_Core_Exception */ - public static function getModeValue($mode = 1) { - self::setModeValues(); + public static function getModeValue($mode = CRM_Contact_BAO_Query::MODE_CONTACTS) { + $searchPane = CRM_Utils_Request::retrieve('searchPane', 'String'); + if (!empty($searchPane)) { + $mode = array_search($searchPane, self::getModeToComponentMapping()); + } + self::setModeValues(); if (!array_key_exists($mode, self::$_modeValues)) { - $mode = 1; + $mode = CRM_Contact_BAO_Query::MODE_CONTACTS; } return self::$_modeValues[$mode]; } + /** + * Get a mapping of modes to components. + * + * This will map the integers to the components. Contact has an empty component + * an pseudo-components exist for activity & related_contact. + * + * @return array + */ + public static function getModeToComponentMapping() { + $mapping = []; + self::setModeValues(); + + foreach (self::$_modeValues as $id => $metadata) { + $mapping[$id] = $metadata['component']; + } + return $mapping; + } + /** * @return array */ public static function getModeSelect() { self::setModeValues(); - $select = array(); + $enabledComponents = CRM_Core_Component::getEnabledComponents(); + $componentModes = []; foreach (self::$_modeValues as $id => & $value) { - $select[$id] = $value['selectorLabel']; + if (strpos($value['component'], 'Civi') !== FALSE + && !array_key_exists($value['component'], $enabledComponents) + ) { + continue; + } + $componentModes[$id] = $value['selectorLabel']; + } + + // unset disabled components + if (!array_key_exists('CiviMail', $enabledComponents)) { + unset($componentModes[CRM_Contact_BAO_Query::MODE_MAILING]); } - // unset contributions or participants if user does not have - // permission on them + // unset contributions or participants if user does not have permission on them if (!CRM_Core_Permission::access('CiviContribute')) { - unset($select['2']); + unset($componentModes[CRM_Contact_BAO_Query::MODE_CONTRIBUTE]); } if (!CRM_Core_Permission::access('CiviEvent')) { - unset($select['3']); + unset($componentModes[CRM_Contact_BAO_Query::MODE_EVENT]); + } + + if (!CRM_Core_Permission::access('CiviMember')) { + unset($componentModes[CRM_Contact_BAO_Query::MODE_MEMBER]); } if (!CRM_Core_Permission::check('view all activities')) { - unset($select['4']); + unset($componentModes[CRM_Contact_BAO_Query::MODE_ACTIVITY]); } - return $select; + + return $componentModes; } /** @@ -325,26 +375,17 @@ public static function getModeSelect() { * @return array */ public function buildTaskList() { + // amtg = 'Add members to group' if ($this->_context !== 'amtg') { - $permission = CRM_Core_Permission::getPermission(); - - if ($this->_componentMode == 1 || $this->_componentMode == 7) { - $this->_taskList += CRM_Contact_Task::permissionedTaskTitles($permission, - CRM_Utils_Array::value('deleted_contacts', $this->_formValues) - ); - } - else { - $className = $this->_modeValue['taskClassName']; - $this->_taskList += $className::permissionedTaskTitles($permission, FALSE); - } - - // Only offer the "Update Smart Group" task if a smart group/saved search is already in play - if (isset($this->_ssID) && $permission == CRM_Core_Permission::EDIT) { - $this->_taskList += CRM_Contact_Task::optionalTaskTitle(); + $taskParams['deletedContacts'] = FALSE; + if ($this->_componentMode == CRM_Contact_BAO_Query::MODE_CONTACTS || $this->_componentMode == CRM_Contact_BAO_Query::MODE_CONTACTSRELATED) { + $taskParams['deletedContacts'] = CRM_Utils_Array::value('deleted_contacts', $this->_formValues); } + $className = $this->_modeValue['taskClassName']; + $taskParams['ssID'] = isset($this->_ssID) ? $this->_ssID : NULL; + $this->_taskList += $className::permissionedTaskTitles(CRM_Core_Permission::getPermission(), $taskParams); } - asort($this->_taskList); return $this->_taskList; } @@ -353,36 +394,19 @@ public function buildTaskList() { */ public function buildQuickForm() { parent::buildQuickForm(); - CRM_Core_Resources::singleton() - // jsTree is needed for tags popup - ->addScriptFile('civicrm', 'packages/jquery/plugins/jstree/jquery.jstree.js', 0, 'html-header', FALSE) - ->addStyleFile('civicrm', 'packages/jquery/plugins/jstree/themes/default/style.css', 0, 'html-header'); - $permission = CRM_Core_Permission::getPermission(); + // some tasks.. what do we want to do with the selected contacts ? - $tasks = array(); - if ($this->_componentMode == 1 || $this->_componentMode == 7) { - $tasks += CRM_Contact_Task::permissionedTaskTitles($permission, - CRM_Utils_Array::value('deleted_contacts', $this->_formValues) - ); - } - else { - $className = $this->_modeValue['taskClassName']; - $tasks += $className::permissionedTaskTitles($permission, FALSE); - } + $this->_taskList = $this->buildTaskList(); if (isset($this->_ssID)) { - if ($permission == CRM_Core_Permission::EDIT) { - $tasks = $tasks + CRM_Contact_Task::optionalTaskTitle(); - } - $search_custom_id = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $this->_ssID, 'search_custom_id'); - $savedSearchValues = array( + $savedSearchValues = [ 'id' => $this->_ssID, 'name' => CRM_Contact_BAO_SavedSearch::getName($this->_ssID, 'title'), 'search_custom_id' => $search_custom_id, - ); + ]; $this->assign_by_ref('savedSearch', $savedSearchValues); $this->assign('ssID', $this->_ssID); } @@ -413,7 +437,7 @@ public function buildQuickForm() { } // set the group title - $groupValues = array('id' => $this->_groupID, 'title' => $this->_group[$this->_groupID]); + $groupValues = ['id' => $this->_groupID, 'title' => $this->_group[$this->_groupID]]; $this->assign_by_ref('group', $groupValues); // also set ssID if this is a saved search @@ -428,10 +452,10 @@ public function buildQuickForm() { } // Set dynamic page title for 'Show Members of Group' - CRM_Utils_System::setTitle(ts('Contacts in Group: %1', array(1 => $this->_group[$this->_groupID]))); + CRM_Utils_System::setTitle(ts('Contacts in Group: %1', [1 => $this->_group[$this->_groupID]])); } - $group_contact_status = array(); + $group_contact_status = []; foreach (CRM_Core_SelectValues::groupContactStatus() as $k => $v) { if (!empty($k)) { $group_contact_status[] = $this->createElement('checkbox', $k, NULL, $v); @@ -455,29 +479,29 @@ public function buildQuickForm() { } // Set dynamic page title for 'Add Members Group' - CRM_Utils_System::setTitle(ts('Add to Group: %1', array(1 => $this->_group[$this->_amtgID]))); + CRM_Utils_System::setTitle(ts('Add to Group: %1', [1 => $this->_group[$this->_amtgID]])); // also set the group title and freeze the action task with Add Members to Group - $groupValues = array('id' => $this->_amtgID, 'title' => $this->_group[$this->_amtgID]); + $groupValues = ['id' => $this->_amtgID, 'title' => $this->_group[$this->_amtgID]]; $this->assign_by_ref('group', $groupValues); - $this->add('submit', $this->_actionButtonName, ts('Add Contacts to %1', array(1 => $this->_group[$this->_amtgID])), - array( + $this->add('submit', $this->_actionButtonName, ts('Add Contacts to %1', [1 => $this->_group[$this->_amtgID]]), + [ 'class' => 'crm-form-submit', - ) + ] ); - $this->add('hidden', 'task', CRM_Contact_Task::GROUP_CONTACTS); - $selectedRowsRadio = $this->addElement('radio', 'radio_ts', NULL, '', 'ts_sel', array('checked' => 'checked')); + $this->add('hidden', 'task', CRM_Contact_Task::GROUP_ADD); + $selectedRowsRadio = $this->addElement('radio', 'radio_ts', NULL, '', 'ts_sel', ['checked' => 'checked']); $allRowsRadio = $this->addElement('radio', 'radio_ts', NULL, '', 'ts_all'); $this->assign('ts_sel_id', $selectedRowsRadio->_attributes['id']); $this->assign('ts_all_id', $allRowsRadio->_attributes['id']); } - $selectedContactIds = array(); + $selectedContactIds = []; $qfKeyParam = CRM_Utils_Array::value('qfKey', $this->_formValues); // We use ajax to handle selections only if the search results component_mode is set to "contacts" - if ($qfKeyParam && ($this->get('component_mode') <= 1 || $this->get('component_mode') == 7)) { + if ($qfKeyParam && ($this->get('component_mode') <= CRM_Contact_BAO_Query::MODE_CONTACTS || $this->get('component_mode') == CRM_Contact_BAO_Query::MODE_CONTACTSRELATED)) { $this->addClass('crm-ajax-selection-form'); $qfKeyParam = "civicrm search {$qfKeyParam}"; - $selectedContactIdsArr = CRM_Core_BAO_PrevNextCache::getSelection($qfKeyParam); + $selectedContactIdsArr = Civi::service('prevnext')->getSelection($qfKeyParam); $selectedContactIds = array_keys($selectedContactIdsArr[$qfKeyParam]); } @@ -515,8 +539,8 @@ public function preProcess() { $this->_ssID = CRM_Utils_Request::retrieve('ssID', 'Positive', $this); $this->_sortByCharacter = CRM_Utils_Request::retrieve('sortByCharacter', 'String', $this); $this->_ufGroupID = CRM_Utils_Request::retrieve('id', 'Positive', $this); - $this->_componentMode = CRM_Utils_Request::retrieve('component_mode', 'Positive', $this, FALSE, 1, $_REQUEST); - $this->_operator = CRM_Utils_Request::retrieve('operator', 'String', $this, FALSE, 1, $_REQUEST, 'AND'); + $this->_componentMode = CRM_Utils_Request::retrieve('component_mode', 'Positive', $this, FALSE, CRM_Contact_BAO_Query::MODE_CONTACTS, $_REQUEST); + $this->_operator = CRM_Utils_Request::retrieve('operator', 'String', $this, FALSE, CRM_Contact_BAO_Query::SEARCH_OPERATOR_AND, 'REQUEST'); /** * set the button names @@ -535,7 +559,7 @@ public function preProcess() { } // assign context to drive the template display, make sure context is valid - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'search'); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'search'); if (!CRM_Utils_Array::value($this->_context, self::validContext())) { $this->_context = 'search'; } @@ -617,23 +641,23 @@ public function preProcess() { // FIXME: we should generalise in a way that components could inject url-filters // just like they build their own form elements - foreach (array( - 'mailing_id', - 'mailing_delivery_status', - 'mailing_open_status', - 'mailing_click_status', - 'mailing_reply_status', - 'mailing_optout', - 'mailing_forward', - 'mailing_unsubscribe', - 'mailing_date_low', - 'mailing_date_high', - ) as $mailingFilter) { + foreach ([ + 'mailing_id', + 'mailing_delivery_status', + 'mailing_open_status', + 'mailing_click_status', + 'mailing_reply_status', + 'mailing_optout', + 'mailing_forward', + 'mailing_unsubscribe', + 'mailing_date_low', + 'mailing_date_high', + ] as $mailingFilter) { $type = 'String'; if ($mailingFilter == 'mailing_id' && $filterVal = CRM_Utils_Request::retrieve('mailing_id', 'Positive', $this) ) { - $this->_formValues[$mailingFilter] = array($filterVal); + $this->_formValues[$mailingFilter] = [$filterVal]; } elseif ($filterVal = CRM_Utils_Request::retrieve($mailingFilter, $type, $this)) { $this->_formValues[$mailingFilter] = $filterVal; @@ -648,9 +672,9 @@ public function preProcess() { $this->assign('id', CRM_Utils_Array::value('uf_group_id', $this->_formValues) ); - $operator = CRM_Utils_Array::value('operator', $this->_formValues, 'AND'); + $operator = CRM_Utils_Array::value('operator', $this->_formValues, CRM_Contact_BAO_Query::SEARCH_OPERATOR_AND); $this->set('queryOperator', $operator); - if ($operator == 'OR') { + if ($operator == CRM_Contact_BAO_Query::SEARCH_OPERATOR_OR) { $this->assign('operator', ts('OR')); } else { @@ -660,17 +684,16 @@ public function preProcess() { // show the context menu only when we’re not searching for deleted contacts; CRM-5673 if (empty($this->_formValues['deleted_contacts'])) { $menuItems = CRM_Contact_BAO_Contact::contextMenu(); - $primaryActions = CRM_Utils_Array::value('primaryActions', $menuItems, array()); - $this->_contextMenu = CRM_Utils_Array::value('moreActions', $menuItems, array()); + $primaryActions = CRM_Utils_Array::value('primaryActions', $menuItems, []); + $this->_contextMenu = CRM_Utils_Array::value('moreActions', $menuItems, []); $this->assign('contextMenu', $primaryActions + $this->_contextMenu); } if (!isset($this->_componentMode)) { $this->_componentMode = CRM_Contact_BAO_Query::MODE_CONTACTS; } - self::setModeValues(); - self::$_selectorName = $this->_modeValue['selectorName']; + self::setModeValues(); $setDynamic = FALSE; if (strpos(self::$_selectorName, 'CRM_Contact_Selector') !== FALSE) { @@ -708,7 +731,7 @@ public function preProcess() { $controller->setDynamicAction($setDynamic); if ($this->_force) { - + $this->loadMetadata(); $this->postProcess(); /* @@ -734,13 +757,6 @@ public function preProcess() { $controller->moveFromSessionToTemplate(); } - /** - * @return array - */ - public function &getFormValues() { - return $this->_formValues; - } - /** * Common post processing. */ @@ -758,14 +774,6 @@ public function postProcess() { //for prev/next pagination $crmPID = CRM_Utils_Request::retrieve('crmPID', 'Integer'); - if (array_key_exists($this->_searchButtonName, $_POST) || - ($this->_force && !$crmPID) - ) { - //reset the cache table for new search - $cacheKey = "civicrm search {$this->controller->_key}"; - CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKey); - } - //get the button name $buttonName = $this->controller->getButtonName(); @@ -803,6 +811,13 @@ public function postProcess() { return; } else { + if (array_key_exists($this->_searchButtonName, $_POST) || + ($this->_force && !$crmPID) + ) { + //reset the cache table for new search + $cacheKey = "civicrm search {$this->controller->_key}"; + Civi::service('prevnext')->deleteItem(NULL, $cacheKey); + } $output = CRM_Core_Selector_Controller::SESSION; // create the selector, controller and run - store results in session @@ -887,4 +902,16 @@ public function getTitle() { return ts('Search'); } + /** + * Load metadata for fields on the form. + * + * @throws \CiviCRM_API3_Exception + */ + protected function loadMetadata() { + // @todo - check what happens if the person does not have 'access civicontribute' - make sure they + // can't by pass acls by passing search criteria in the url. + $this->addSearchFieldMetadata(['Contribution' => CRM_Contribute_BAO_Query::getSearchFieldMetadata()]); + $this->addSearchFieldMetadata(['ContributionRecur' => CRM_Contribute_BAO_ContributionRecur::getContributionRecurSearchFieldMetadata()]); + } + } diff --git a/CRM/Contact/Form/Search/Advanced.php b/CRM/Contact/Form/Search/Advanced.php index d17106838786..a3f46e68de23 100644 --- a/CRM/Contact/Form/Search/Advanced.php +++ b/CRM/Contact/Form/Search/Advanced.php @@ -1,9 +1,9 @@ _ssID)) { $this->_ssID = $this->get('ssID'); } - $defaults = array_merge($this->_formValues, array( + $defaults = array_merge($this->_formValues, [ 'privacy_toggle' => 1, 'operator' => 'AND', - )); + ], $defaults); $this->normalizeDefaultValues($defaults); if ($this->_context === 'amtg') { - $defaults['task'] = CRM_Contact_Task::GROUP_CONTACTS; + $defaults['task'] = CRM_Contact_Task::GROUP_ADD; } - return $defaults; } @@ -227,18 +228,18 @@ public function setDefaultValues() { public function postProcess() { $this->set('isAdvanced', '1'); + $this->setFormValues(); // get user submitted values // get it from controller only if form has been submitted, else preProcess has set this if (!empty($_POST)) { - $this->_formValues = $this->controller->exportValues($this->_name); $this->normalizeFormValues(); // FIXME: couldn't figure out a good place to do this, // FIXME: so leaving this as a dependency for now if (array_key_exists('contribution_amount_low', $this->_formValues)) { foreach (array( - 'contribution_amount_low', - 'contribution_amount_high', - ) as $f) { + 'contribution_amount_low', + 'contribution_amount_high', + ) as $f) { $this->_formValues[$f] = CRM_Utils_Rule::cleanMoney($this->_formValues[$f]); } } @@ -269,11 +270,11 @@ public function postProcess() { !$this->_force ) { foreach (array( - 'case_type_id', - 'case_status_id', - 'case_deleted', - 'case_tags', - ) as $caseCriteria) { + 'case_type_id', + 'case_status_id', + 'case_deleted', + 'case_tags', + ) as $caseCriteria) { if (!empty($this->_formValues[$caseCriteria])) { $allCases = TRUE; $this->_formValues['case_owner'] = 1; @@ -348,8 +349,6 @@ public function normalizeFormValues() { 'activity_type_id', 'status_id', 'priority_id', - 'activity_subject', - 'activity_details', 'contribution_page_id', 'contribution_product_id', 'payment_instrument_id', @@ -387,10 +386,7 @@ public function normalizeFormValues() { * * @return array */ - public function normalizeDefaultValues(&$defaults) { - if (!is_array($defaults)) { - $defaults = array(); - } + public function normalizeDefaultValues($defaults) { $this->loadDefaultCountryBasedOnState($defaults); if ($this->_ssID && empty($_POST)) { $defaults = array_merge($defaults, CRM_Contact_BAO_SavedSearch::getFormValues($this->_ssID)); @@ -403,7 +399,7 @@ public function normalizeDefaultValues(&$defaults) { * id of the tagset. */ if (isset($defaults['contact_tags'])) { - foreach ($defaults['contact_tags'] as $key => $tagId) { + foreach ((array) $defaults['contact_tags'] as $key => $tagId) { if (!is_array($tagId)) { $parentId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Tag', $tagId, 'parent_id'); $element = "contact_taglist[$parentId]"; diff --git a/CRM/Contact/Form/Search/Basic.php b/CRM/Contact/Form/Search/Basic.php index 057f54f13a5f..9a3386836dbb 100644 --- a/CRM/Contact/Form/Search/Basic.php +++ b/CRM/Contact/Form/Search/Basic.php @@ -1,9 +1,9 @@ ts('- any contact type -')) + CRM_Contact_BAO_ContactType::getSelectElements(); + $contactTypes = ['' => ts('- any contact type -')] + CRM_Contact_BAO_ContactType::getSelectElements(); $this->add('select', 'contact_type', ts('is...'), $contactTypes, FALSE, - array('class' => 'crm-select2') + ['class' => 'crm-select2'] ); } // add select for groups + // Get hierarchical listing of groups, respecting ACLs for CRM-16836. + $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($this->_group, NULL, '  ', TRUE); if (!empty($searchOptions['groups'])) { - $this->addField('group', array( - 'entity' => 'group_contact', - 'label' => ts('in'), - 'placeholder' => ts('- any group -'), - )); + $this->addField('group', [ + 'entity' => 'group_contact', + 'label' => ts('in'), + 'placeholder' => ts('- any group -'), + 'options' => $groupHierarchy, + ]); } if (!empty($searchOptions['tags'])) { // tag criteria if (!empty($this->_tag)) { - $this->addField('tag', array( - 'entity' => 'entity_tag', - 'label' => ts('with'), - 'placeholder' => ts('- any tag -'), - )); + $this->addField('tag', [ + 'entity' => 'entity_tag', + 'label' => ts('with'), + 'placeholder' => ts('- any tag -'), + ]); } } @@ -94,7 +97,7 @@ public function buildQuickForm() { * the default array reference */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; $defaults['sort_name'] = CRM_Utils_Array::value('sort_name', $this->_formValues); foreach (self::$csv as $v) { @@ -108,7 +111,7 @@ public function setDefaultValues() { } if ($this->_context === 'amtg') { - $defaults['task'] = CRM_Contact_Task::GROUP_CONTACTS; + $defaults['task'] = CRM_Contact_Task::GROUP_ADD; } if ($this->_context === 'smog') { @@ -122,7 +125,7 @@ public function setDefaultValues() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_Search_Basic', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Search_Basic', 'formRule']); } /** @@ -194,14 +197,16 @@ public function postProcess() { * If Go is pressed then we must select some checkboxes and an action. * * @param array $fields + * @param array $files + * @param object $form * * @return array|bool */ - public static function formRule($fields) { + public static function formRule($fields, $files, $form) { // check actionName and if next, then do not repeat a search, since we are going to the next page if (array_key_exists('_qf_Search_next', $fields)) { if (empty($fields['task'])) { - return array('task' => 'Please select a valid action.'); + return ['task' => 'Please select a valid action.']; } if (CRM_Utils_Array::value('task', $fields) == CRM_Contact_Task::SAVE_SEARCH) { @@ -219,7 +224,7 @@ public static function formRule($fields) { return TRUE; } } - return array('task' => 'Please select one or more checkboxes to perform the action on.'); + return ['task' => 'Please select one or more checkboxes to perform the action on.']; } return TRUE; } @@ -229,6 +234,7 @@ public static function formRule($fields) { * * @return string */ + /** * @return string */ diff --git a/CRM/Contact/Form/Search/Builder.php b/CRM/Contact/Form/Search/Builder.php index b052193a0c72..2a73f11a250f 100644 --- a/CRM/Contact/Form/Search/Builder.php +++ b/CRM/Contact/Form/Search/Builder.php @@ -1,9 +1,9 @@ $field) { - if (strpos($name, '_date') || CRM_Utils_Array::value('data_type', $field) == 'Date') { - $dateFields[] = $name; - } - // it's necessary to know which of the fields are from string data type - if (isset($field['type']) && $field['type'] === CRM_Utils_Type::T_STRING) { - $stringFields[] = $name; - } + // Assign date type to respective field name, which will be later used to modify operator list + $fieldNameTypes[$name] = CRM_Utils_Type::typeToString(CRM_Utils_Array::value('type', $field)); // it's necessary to know which of the fields are searchable by label if (isset($field['searchByLabel']) && $field['searchByLabel']) { $searchByLabelFields[] = $name; @@ -112,18 +106,16 @@ public function buildQuickForm() { // Add javascript CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/Contact/Form/Search/Builder.js', 1, 'html-header') - ->addSetting(array( - 'searchBuilder' => array( + ->addSetting([ + 'searchBuilder' => [ // Index of newly added/expanded block (1-based index) 'newBlock' => $this->get('newBlock'), - 'dateFields' => $dateFields, 'fieldOptions' => self::fieldOptions(), - 'stringFields' => $stringFields, 'searchByLabelFields' => $searchByLabelFields, - 'generalOperators' => array('' => ts('-operator-')) + CRM_Core_SelectValues::getSearchBuilderOperators(), - 'stringOperators' => array('' => ts('-operator-')) + CRM_Core_SelectValues::getSearchBuilderOperators(CRM_Utils_Type::T_STRING), - ), - )); + 'fieldTypes' => $fieldNameTypes, + 'generalOperators' => ['' => ts('-operator-')] + CRM_Core_SelectValues::getSearchBuilderOperators(), + ], + ]); //get the saved search mapping id $mappingId = NULL; if ($this->_ssID) { @@ -139,7 +131,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_Search_Builder', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Search_Builder', 'formRule'], $this); } /** @@ -159,7 +151,7 @@ public static function formRule($values, $files, $self) { $fields = self::fields(); $fld = CRM_Core_BAO_Mapping::formattedFields($values, TRUE); - $errorMsg = array(); + $errorMsg = []; foreach ($fld as $k => $v) { if (!$v[1]) { $errorMsg["operator[$v[3]][$v[4]]"] = ts("Please enter the operator."); @@ -168,19 +160,17 @@ public static function formRule($values, $files, $self) { // CRM-10338 $v[2] = self::checkArrayKeyEmpty($v[2]); - if (in_array($v[1], array( - 'IS NULL', - 'IS NOT NULL', - 'IS EMPTY', - 'IS NOT EMPTY', - )) && - !empty($v[2]) - ) { - $errorMsg["value[$v[3]][$v[4]]"] = ts('Please clear your value if you want to use %1 operator.', array(1 => $v[1])); + if (in_array($v[1], [ + 'IS NULL', + 'IS NOT NULL', + 'IS EMPTY', + 'IS NOT EMPTY', + ]) && !empty($v[2])) { + $errorMsg["value[$v[3]][$v[4]]"] = ts('Please clear your value if you want to use %1 operator.', [1 => $v[1]]); } elseif (substr($v[0], 0, 7) === 'do_not_' or substr($v[0], 0, 3) === 'is_') { if (isset($v[2])) { - $v2 = array($v[2]); + $v2 = [$v[2]]; if (!isset($v[2])) { $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value."); } @@ -201,10 +191,10 @@ public static function formRule($values, $files, $self) { $type = $fields[$fieldKey]['data_type']; // hack to handle custom data of type state and country - if (in_array($type, array( + if (in_array($type, [ 'Country', 'StateProvince', - ))) { + ])) { $type = "Integer"; } } @@ -227,7 +217,7 @@ public static function formRule($values, $files, $self) { } // Check Empty values for Integer Or Boolean Or Date type For operators other than IS NULL and IS NOT NULL. elseif (!in_array($v[1], - array('IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY')) + ['IS NULL', 'IS NOT NULL', 'IS EMPTY', 'IS NOT EMPTY']) ) { if ((($type == 'Int' || $type == 'Boolean') && !is_array($v[2]) && !trim($v[2])) && $v[2] != '0') { $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a value."); @@ -258,7 +248,7 @@ public static function formRule($values, $files, $self) { // Validate each value in parenthesis to avoid db errors if (empty($errorMsg)) { - $parenValues = array(); + $parenValues = []; $parenValues = is_array($v[2]) ? (array_key_exists($v[1], $v[2])) ? $v[2][$v[1]] : $v[2] : explode(',', trim($inVal, "(..)")); foreach ($parenValues as $val) { if ($type == 'Date' || $type == 'Timestamp') { @@ -284,6 +274,12 @@ public static function formRule($values, $files, $self) { } elseif (trim($v[2])) { //else check value for rest of the Operators + if ($type == 'Date' || $type == 'Timestamp') { + $v[2] = CRM_Utils_Date::processDate($v[2]); + if ($type == 'Date') { + $v[2] = substr($v[2], 0, 8); + } + } $error = CRM_Utils_Type::validate($v[2], $type, FALSE); if ($error != $v[2]) { $errorMsg["value[$v[3]][$v[4]]"] = ts("Please enter a valid value."); @@ -440,7 +436,7 @@ public static function fields() { public static function fieldOptions() { // Hack to add options not retrieved by getfields // This list could go on and on, but it would be better to fix getfields - $options = array( + $options = [ 'group' => 'group_contact', 'tag' => 'entity_tag', 'on_hold' => 'yesno', @@ -452,8 +448,8 @@ public static function fieldOptions() { 'member_is_test' => 'yesno', 'member_is_pay_later' => 'yesno', 'is_override' => 'yesno', - ); - $entities = array( + ]; + $entities = [ 'contact', 'address', 'activity', @@ -463,7 +459,7 @@ public static function fieldOptions() { 'contribution', 'case', 'grant', - ); + ]; CRM_Contact_BAO_Query_Hook::singleton()->alterSearchBuilderOptions($entities, $options); foreach ($entities as $entity) { $fields = civicrm_api3($entity, 'getfields'); @@ -475,13 +471,15 @@ public static function fieldOptions() { $options[substr($field, 0, -3)] = $entity; } } - elseif (!empty($info['data_type']) && in_array($info['data_type'], array('StateProvince', 'Country'))) { - $options[$field] = $entity; + elseif (!empty($info['data_type'])) { + if (in_array($info['data_type'], ['StateProvince', 'Country'])) { + $options[$field] = $entity; + } } - elseif (in_array(substr($field, 0, 3), array( - 'is_', - 'do_', - )) || CRM_Utils_Array::value('data_type', $info) == 'Boolean' + elseif (in_array(substr($field, 0, 3), [ + 'is_', + 'do_', + ]) || CRM_Utils_Array::value('data_type', $info) == 'Boolean' ) { $options[$field] = 'yesno'; if ($entity != 'contact') { diff --git a/CRM/Contact/Form/Search/Criteria.php b/CRM/Contact/Form/Search/Criteria.php index b6280d270255..d6f731b0a844 100644 --- a/CRM/Contact/Form/Search/Criteria.php +++ b/CRM/Contact/Form/Search/Criteria.php @@ -1,9 +1,9 @@ addSearchFieldMetadata(['Contact' => self::getSearchFieldMetadata()]); + $form->addFormFieldsFromMetadata(); + self::setBasicSearchFields($form); $form->addElement('hidden', 'hidden_basic', 1); if ($form->_searchOptions['contactType']) { @@ -42,7 +48,7 @@ public static function basic(&$form) { if ($contactTypes) { $form->add('select', 'contact_type', ts('Contact Type(s)'), $contactTypes, FALSE, - array('id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;') + ['id' => 'contact_type', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;'] ); } } @@ -54,11 +60,11 @@ public static function basic(&$form) { $groupHierarchy = CRM_Contact_BAO_Group::getGroupsHierarchy($form->_group, NULL, '  ', TRUE); $form->add('select', 'group', ts('Groups'), $groupHierarchy, FALSE, - array('id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2') + ['id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2'] ); $groupOptions = CRM_Core_BAO_OptionValue::getOptionValuesAssocArrayFromName('group_type'); $form->add('select', 'group_type', ts('Group Types'), $groupOptions, FALSE, - array('id' => 'group_type', 'multiple' => 'multiple', 'class' => 'crm-select2') + ['id' => 'group_type', 'multiple' => 'multiple', 'class' => 'crm-select2'] ); $form->add('hidden', 'group_search_selected', 'group'); } @@ -69,8 +75,8 @@ public static function basic(&$form) { $contactTags = CRM_Core_BAO_Tag::getTags(); if ($contactTags) { - $form->add('select', 'contact_tags', ts('Tags'), $contactTags, FALSE, - array('id' => 'contact_tags', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;') + $form->add('select', 'contact_tags', ts('Select Tag(s)'), $contactTags, FALSE, + ['id' => 'contact_tags', 'multiple' => 'multiple', 'class' => 'crm-select2', 'style' => 'width: 100%;'] ); } @@ -78,7 +84,7 @@ public static function basic(&$form) { CRM_Core_Form_Tag::buildQuickForm($form, $parentNames, 'civicrm_contact', NULL, TRUE, FALSE); $used_for = CRM_Core_OptionGroup::values('tag_used_for'); - $tagsTypes = array(); + $tagsTypes = []; $showAllTagTypes = FALSE; foreach ($used_for as $key => $value) { //check tags for every type and find if there are any defined @@ -87,23 +93,17 @@ public static function basic(&$form) { // we will hide searching contact by attachments tags until it will be implemented in core if (count($tags) && $key != 'civicrm_file' && $key != 'civicrm_contact') { //if tags exists then add type to display in adv search form help text - $tagsTypes[] = ts($value); + $tagsTypes[] = $value; $showAllTagTypes = TRUE; } } $tagTypesText = implode(" or ", $tagsTypes); if ($showAllTagTypes) { - $form->add('checkbox', 'all_tag_types', ts('Include tags used for %1', array(1 => $tagTypesText))); + $form->add('checkbox', 'all_tag_types', ts('Include tags used for %1', [1 => $tagTypesText])); $form->add('hidden', 'tag_types_text', $tagTypesText); } } - // add text box for last name, first name, street name, city - $form->addElement('text', 'sort_name', ts('Find...'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')); - - // add text box for last name, first name, street name, city - $form->add('text', 'email', ts('Contact Email'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')); - //added contact source $form->add('text', 'contact_source', ts('Contact Source'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'contact_source')); @@ -111,7 +111,7 @@ public static function basic(&$form) { $form->addElement('text', 'job_title', ts('Job Title'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'job_title')); //added internal ID - $form->add('number', 'contact_id', ts('Contact ID'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'id') + array('min' => 1)); + $form->add('number', 'contact_id', ts('Contact ID'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'id') + ['min' => 1]); $form->addRule('contact_id', ts('Please enter valid Contact ID'), 'positiveInteger'); //added external ID @@ -131,16 +131,16 @@ public static function basic(&$form) { // FIXME: This is probably a part of profiles - need to be // FIXME: eradicated from here when profiles are reworked. - $types = array('Participant', 'Contribution', 'Membership'); + $types = ['Participant', 'Contribution', 'Membership']; // get component profiles - $componentProfiles = array(); + $componentProfiles = []; $componentProfiles = CRM_Core_BAO_UFGroup::getProfiles($types); $ufGroups = CRM_Core_BAO_UFGroup::getModuleUFGroup('Search Profile', 1); $accessibleUfGroups = CRM_Core_Permission::ufGroup(CRM_Core_Permission::VIEW); - $searchProfiles = array(); + $searchProfiles = []; foreach ($ufGroups as $key => $var) { if (!array_key_exists($key, $componentProfiles) && in_array($key, $accessibleUfGroups)) { $searchProfiles[$key] = $var['title']; @@ -150,63 +150,38 @@ public static function basic(&$form) { $form->add('select', 'uf_group_id', ts('Views For Display Contacts'), - array( + [ '0' => ts('- default view -'), - ) + $searchProfiles, + ] + $searchProfiles, FALSE, - array('class' => 'crm-select2') + ['class' => 'crm-select2'] ); $componentModes = CRM_Contact_Form_Search::getModeSelect(); - $enabledComponents = CRM_Core_Component::getEnabledComponents(); - - // unset disabled components that must should have been enabled - // to the option be viable - if (!array_key_exists('CiviMail', $enabledComponents)) { - unset($componentModes['8']); - } - - // unset contributions or participants if user does not have - // permission on them - if (!CRM_Core_Permission::access('CiviContribute')) { - unset($componentModes['2']); - } - - if (!CRM_Core_Permission::access('CiviEvent')) { - unset($componentModes['3']); - } - - if (!CRM_Core_Permission::access('CiviMember')) { - unset($componentModes['5']); - } - - if (!CRM_Core_Permission::check('view all activities')) { - unset($componentModes['4']); - } - + $form->assign('component_mappings', json_encode(CRM_Contact_Form_Search::getModeToComponentMapping())); if (count($componentModes) > 1) { $form->add('select', 'component_mode', ts('Display Results As'), $componentModes, FALSE, - array('class' => 'crm-select2') + ['class' => 'crm-select2'] ); } $form->addRadio( 'operator', ts('Search Operator'), - array( - 'AND' => ts('AND'), - 'OR' => ts('OR'), - ), - array('allowClear' => FALSE) + [ + CRM_Contact_BAO_Query::SEARCH_OPERATOR_AND => ts('AND'), + CRM_Contact_BAO_Query::SEARCH_OPERATOR_OR => ts('OR'), + ], + ['allowClear' => FALSE] ); // add the option to display relationships $rTypes = CRM_Core_PseudoConstant::relationshipType(); - $rSelect = array('' => ts('- Select Relationship Type-')); + $rSelect = ['' => ts('- Select Relationship Type-')]; foreach ($rTypes as $rid => $rValue) { if ($rValue['label_a_b'] == $rValue['label_b_a']) { $rSelect[$rid] = $rValue['label_a_b']; @@ -221,7 +196,7 @@ public static function basic(&$form) { 'display_relationship_type', ts('Display Results as Relationship'), $rSelect, - array('class' => 'crm-select2') + ['class' => 'crm-select2'] ); // checkboxes for DO NOT phone, email, mail @@ -232,47 +207,150 @@ public static function basic(&$form) { ts('Privacy'), $t, FALSE, - array( + [ 'id' => 'privacy_options', 'multiple' => 'multiple', 'class' => 'crm-select2', - ) + ] ); $form->addElement('select', 'privacy_operator', ts('Operator'), - array( + [ 'OR' => ts('OR'), 'AND' => ts('AND'), - ) + ] ); - $options = array( + $options = [ 1 => ts('Exclude'), 2 => ts('Include by Privacy Option(s)'), - ); - $form->addRadio('privacy_toggle', ts('Privacy Options'), $options, array('allowClear' => FALSE)); + ]; + $form->addRadio('privacy_toggle', ts('Privacy Options'), $options, ['allowClear' => FALSE]); // preferred communication method - - $onHold[] = $form->createElement('advcheckbox', 'on_hold', NULL, ''); - $form->addGroup($onHold, 'email_on_hold', ts('Email On Hold')); + if (Civi::settings()->get('civimail_multiple_bulk_emails')) { + $form->addSelect('email_on_hold', + ['entity' => 'email', 'multiple' => 'multiple', 'label' => ts('Email On Hold'), 'options' => CRM_Core_PseudoConstant::emailOnHoldOptions()]); + } + else { + $form->add('advcheckbox', 'email_on_hold', ts('Email On Hold')); + } $form->addSelect('preferred_communication_method', - array('entity' => 'contact', 'multiple' => 'multiple', 'label' => ts('Preferred Communication Method'), 'option_url' => NULL, 'placeholder' => ts('- any -'))); + ['entity' => 'contact', 'multiple' => 'multiple', 'label' => ts('Preferred Communication Method'), 'option_url' => NULL, 'placeholder' => ts('- any -')]); //CRM-6138 Preferred Language - $form->addSelect('preferred_language', array('class' => 'twenty', 'context' => 'search')); + $form->addSelect('preferred_language', ['class' => 'twenty', 'context' => 'search']); // Phone search $form->addElement('text', 'phone_numeric', ts('Phone'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone', 'phone')); $locationType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $phoneType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'); - $form->add('select', 'phone_location_type_id', ts('Phone Location'), array('' => ts('- any -')) + $locationType, FALSE, array('class' => 'crm-select2')); - $form->add('select', 'phone_phone_type_id', ts('Phone Type'), array('' => ts('- any -')) + $phoneType, FALSE, array('class' => 'crm-select2')); + $form->add('select', 'phone_location_type_id', ts('Phone Location'), ['' => ts('- any -')] + $locationType, FALSE, ['class' => 'crm-select2']); + $form->add('select', 'phone_phone_type_id', ts('Phone Type'), ['' => ts('- any -')] + $phoneType, FALSE, ['class' => 'crm-select2']); } + /** + * Get the metadata for fields to be included on the contact search form. + * + * @throws \CiviCRM_API3_Exception + */ + public static function getSearchFieldMetadata() { + $fields = [ + 'sort_name' => ['title' => ts('Complete OR Partial Name'), 'template_grouping' => 'basic'], + 'email' => ['title' => ts('Complete OR Partial Email'), 'entity' => 'Email', 'template_grouping' => 'basic'], + 'contact_tags' => ['name' => 'contact_tags', 'type' => CRM_Utils_Type::T_INT, 'is_pseudofield' => TRUE, 'template_grouping' => 'basic'], + ]; + $metadata = civicrm_api3('Contact', 'getfields', [])['values']; + foreach ($fields as $fieldName => $field) { + $fields[$fieldName] = array_merge(CRM_Utils_Array::value($fieldName, $metadata, []), $field); + } + return $fields; + } + + /** + * Defines the fields that can be displayed for the basic search section. + * + * @param CRM_Core_Form $form + */ + protected static function setBasicSearchFields($form) { + $searchFields = []; + foreach (self::getSearchFieldMetadata() as $fieldName => $field) { + if ($field['template_grouping'] === 'basic') { + $searchFields[$fieldName] = $field; + } + } + $form->assign('basicSearchFields', array_merge(self::getBasicSearchFields(), $searchFields)); + } + + /** + * Return list of basic contact fields that can be displayed for the basic search section. + * + */ + public static function getBasicSearchFields() { + $userFramework = CRM_Core_Config::singleton()->userFramework; + return [ + // For now an empty array is still left in place for ordering. + 'sort_name' => [], + 'email' => ['name' => 'email'], + 'contact_type' => ['name' => 'contact_type'], + 'group' => [ + 'name' => 'group', + 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/group.tpl', + ], + 'contact_tags' => ['name' => 'contact_tags'], + 'tag_types_text' => ['name' => 'tag_types_text'], + 'tag_search' => [ + 'name' => 'tag_search', + 'help' => ['id' => 'id-all-tags'], + ], + 'tag_set' => [ + 'name' => 'tag_set', + 'is_custom' => TRUE, + 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/tag_set.tpl', + ], + 'all_tag_types' => [ + 'name' => 'all_tag_types', + 'class' => 'search-field__span-3 search-field__checkbox', + 'help' => ['id' => 'id-all-tag-types'], + ], + 'phone_numeric' => [ + 'name' => 'phone_numeric', + 'description' => ts('Punctuation and spaces are ignored.'), + ], + 'phone_location_type_id' => ['name' => 'phone_location_type_id'], + 'phone_phone_type_id' => ['name' => 'phone_phone_type_id'], + 'privacy_toggle' => [ + 'name' => 'privacy_toggle', + 'class' => 'search-field__span-2', + 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/privacy_toggle.tpl', + ], + 'preferred_communication_method' => [ + 'name' => 'preferred_communication_method', + 'template' => 'CRM/Contact/Form/Search/Criteria/Fields/preferred_communication_method.tpl', + ], + 'contact_source' => [ + 'name' => 'contact_source', + 'help' => ['id' => 'id-source', 'file' => 'CRM/Contact/Form/Contact'], + ], + 'job_title' => ['name' => 'job_title'], + 'preferred_language' => ['name' => 'preferred_language'], + 'contact_id' => [ + 'name' => 'contact_id', + 'help' => ['id' => 'id-contact-id', 'file' => 'CRM/Contact/Form/Contact'], + ], + 'external_identifier' => [ + 'name' => 'external_identifier', + 'help' => ['id' => 'id-external-id', 'file' => 'CRM/Contact/Form/Contact'], + ], + 'uf_user' => [ + 'name' => 'uf_user', + 'description' => ts('Does the contact have a %1 Account?', [$userFramework]), + ], + ]; + } /** * @param CRM_Core_Form $form @@ -295,21 +373,21 @@ public static function location(&$form) { $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_Address'); - $elements = array( - 'street_address' => array(ts('Street Address'), $attributes['street_address'], NULL, NULL), - 'supplemental_address_1' => array(ts('Supplemental Address 1'), $attributes['supplemental_address_1'], NULL, NULL), - 'supplemental_address_2' => array(ts('Supplemental Address 2'), $attributes['supplemental_address_2'], NULL, NULL), - 'supplemental_address_3' => array(ts('Supplemental Address 3'), $attributes['supplemental_address_3'], NULL, NULL), - 'city' => array(ts('City'), $attributes['city'], NULL, NULL), - 'postal_code' => array(ts('Postal Code'), $attributes['postal_code'], NULL, NULL), - 'country' => array(ts('Country'), $attributes['country_id'], 'country', FALSE), - 'state_province' => array(ts('State/Province'), $attributes['state_province_id'], 'stateProvince', TRUE), - 'county' => array(ts('County'), $attributes['county_id'], 'county', TRUE), - 'address_name' => array(ts('Address Name'), $attributes['address_name'], NULL, NULL), - 'street_number' => array(ts('Street Number'), $attributes['street_number'], NULL, NULL), - 'street_name' => array(ts('Street Name'), $attributes['street_name'], NULL, NULL), - 'street_unit' => array(ts('Apt/Unit/Suite'), $attributes['street_unit'], NULL, NULL), - ); + $elements = [ + 'street_address' => [ts('Street Address'), $attributes['street_address'], NULL, NULL], + 'supplemental_address_1' => [ts('Supplemental Address 1'), $attributes['supplemental_address_1'], NULL, NULL], + 'supplemental_address_2' => [ts('Supplemental Address 2'), $attributes['supplemental_address_2'], NULL, NULL], + 'supplemental_address_3' => [ts('Supplemental Address 3'), $attributes['supplemental_address_3'], NULL, NULL], + 'city' => [ts('City'), $attributes['city'], NULL, NULL], + 'postal_code' => [ts('Postal Code'), $attributes['postal_code'], NULL, NULL], + 'country' => [ts('Country'), $attributes['country_id'], 'country', FALSE], + 'state_province' => [ts('State/Province'), $attributes['state_province_id'], 'stateProvince', TRUE], + 'county' => [ts('County'), $attributes['county_id'], 'county', TRUE], + 'address_name' => [ts('Address Name'), $attributes['address_name'], NULL, NULL], + 'street_number' => [ts('Street Number'), $attributes['street_number'], NULL, NULL], + 'street_name' => [ts('Street Name'), $attributes['street_name'], NULL, NULL], + 'street_unit' => [ts('Apt/Unit/Suite'), $attributes['street_unit'], NULL, NULL], + ]; $parseStreetAddress = CRM_Utils_Array::value('street_address_parsing', $addressOptions, 0); $form->assign('parseStreetAddress', $parseStreetAddress); @@ -317,7 +395,7 @@ public static function location(&$form) { list($title, $attributes, $select, $multiSelect) = $v; if (in_array($name, - array('street_number', 'street_name', 'street_unit') + ['street_number', 'street_name', 'street_unit'] )) { if (!$parseStreetAddress) { continue; @@ -336,8 +414,8 @@ public static function location(&$form) { $element = $form->addChainSelect($name); } else { - $selectElements = array('' => ts('- any -')) + CRM_Core_PseudoConstant::$select(); - $element = $form->add('select', $name, $title, $selectElements, FALSE, array('class' => 'crm-select2')); + $selectElements = ['' => ts('- any -')] + CRM_Core_PseudoConstant::$select(); + $element = $form->add('select', $name, $title, $selectElements, FALSE, ['class' => 'crm-select2']); } if ($multiSelect) { $element->setMultiple(TRUE); @@ -348,34 +426,34 @@ public static function location(&$form) { } if ($addressOptions['postal_code']) { - $attr = array('class' => 'six') + (array) CRM_Utils_Array::value('postal_code', $attributes); - $form->addElement('text', 'postal_code_low', NULL, $attr + array('placeholder' => ts('From'))); - $form->addElement('text', 'postal_code_high', NULL, $attr + array('placeholder' => ts('To'))); + $attr = ['class' => 'six'] + (array) CRM_Utils_Array::value('postal_code', $attributes); + $form->addElement('text', 'postal_code_low', NULL, $attr + ['placeholder' => ts('From')]); + $form->addElement('text', 'postal_code_high', NULL, $attr + ['placeholder' => ts('To')]); } } // extend addresses with proximity search - if (!empty($config->geocodeMethod)) { - $form->addElement('text', 'prox_distance', ts('Find contacts within'), array('class' => 'six')); - $form->addElement('select', 'prox_distance_unit', NULL, array( + if (CRM_Utils_GeocodeProvider::getUsableClassName()) { + $form->addElement('text', 'prox_distance', ts('Find contacts within'), ['class' => 'six']); + $form->addElement('select', 'prox_distance_unit', NULL, [ 'miles' => ts('Miles'), 'kilos' => ts('Kilometers'), - )); + ]); $form->addRule('prox_distance', ts('Please enter positive number as a distance'), 'numeric'); } - $form->addSelect('world_region', array('entity' => 'address', 'context' => 'search')); + $form->addSelect('world_region', ['entity' => 'address', 'context' => 'search']); // select for location type $locationType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $form->add('select', 'location_type', ts('Address Location'), $locationType, FALSE, array( + $form->add('select', 'location_type', ts('Address Location'), $locationType, FALSE, [ 'multiple' => TRUE, 'class' => 'crm-select2', 'placeholder' => ts('Primary'), - )); + ]); // custom data extending addresses - CRM_Core_BAO_Query::addCustomFormFields($form, array('Address')); + CRM_Core_BAO_Query::addCustomFormFields($form, ['Address']); } /** @@ -395,10 +473,10 @@ public static function changeLog(&$form) { // block for change log $form->addElement('text', 'changed_by', ts('Modified By'), NULL); - $dates = array(1 => ts('Added'), 2 => ts('Modified')); - $form->addRadio('log_date', NULL, $dates, array('allowClear' => TRUE), '
    '); + $dates = [1 => ts('Added'), 2 => ts('Modified')]; + $form->addRadio('log_date', NULL, $dates, ['allowClear' => TRUE]); - CRM_Core_Form_Date::buildDateRange($form, 'log_date', 1, '_low', '_high', ts('From'), FALSE, FALSE); + CRM_Core_Form_Date::buildDateRange($form, 'log_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); } /** @@ -409,61 +487,63 @@ public static function task(&$form) { } /** - * @param $form + * @param CRM_Core_Form_Search $form */ public static function relationship(&$form) { $form->add('hidden', 'hidden_relationship', 1); - $allRelationshipType = array(); + $form->add('text', 'relation_description', ts('Description'), ['class' => 'twenty']); $allRelationshipType = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, NULL, TRUE); - $form->add('select', 'relation_type_id', ts('Relationship Type'), array('' => ts('- select -')) + $allRelationshipType, FALSE, array('class' => 'crm-select2')); + $form->add('select', 'relation_type_id', ts('Relationship Type'), ['' => ts('- select -')] + $allRelationshipType, FALSE, ['multiple' => TRUE, 'class' => 'crm-select2']); $form->addElement('text', 'relation_target_name', ts('Target Contact'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')); // relation status - $relStatusOption = array(ts('Active'), ts('Inactive'), ts('All')); + $relStatusOption = [ts('Active'), ts('Inactive'), ts('All')]; $form->addRadio('relation_status', ts('Relationship Status'), $relStatusOption); - $form->setDefaults(array('relation_status' => 0)); + $form->setDefaults(['relation_status' => 0]); // relation permission - $relPermissionOption = array(ts('Any'), ts('Yes'), ts('No')); - $form->addRadio('relation_permission', ts('Permissioned Relationship?'), $relPermissionOption); - $form->setDefaults(array('relation_permission' => 0)); + $allRelationshipPermissions = CRM_Contact_BAO_Relationship::buildOptions('is_permission_a_b'); + $form->add('select', 'relation_permission', ts('Permissioned Relationship'), + ['' => ts('- select -')] + $allRelationshipPermissions, FALSE, ['multiple' => TRUE, 'class' => 'crm-select2']); //add the target group if ($form->_group) { $form->add('select', 'relation_target_group', ts('Target Contact(s) in Group'), $form->_group, FALSE, - array('id' => 'relation_target_group', 'multiple' => 'multiple', 'class' => 'crm-select2') + ['id' => 'relation_target_group', 'multiple' => 'multiple', 'class' => 'crm-select2'] ); } CRM_Core_Form_Date::buildDateRange($form, 'relation_start_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); CRM_Core_Form_Date::buildDateRange($form, 'relation_end_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); + CRM_Core_Form_Date::buildDateRange($form, 'relation_active_period_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); + // Add reltionship dates CRM_Core_Form_Date::buildDateRange($form, 'relation_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); // add all the custom searchable fields - CRM_Core_BAO_Query::addCustomFormFields($form, array('Relationship')); + CRM_Core_BAO_Query::addCustomFormFields($form, ['Relationship']); } /** - * @param $form + * @param CRM_Core_Form_Search $form */ public static function demographics(&$form) { $form->add('hidden', 'hidden_demographics', 1); // radio button for gender - $genderOptions = array(); + $genderOptions = []; $gender = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'); foreach ($gender as $key => $var) { $genderOptions[$key] = $form->createElement('radio', NULL, ts('Gender'), $var, $key, - array('id' => "civicrm_gender_{$var}_{$key}") + ['id' => "civicrm_gender_{$var}_{$key}"] ); } $form->addGroup($genderOptions, 'gender_id', ts('Gender'))->setAttribute('allowClear', TRUE); - $form->add('text', 'age_low', ts('Min Age'), array('size' => 6)); + $form->add('number', 'age_low', ts('Min Age'), ['class' => 'four', 'min' => 0]); $form->addRule('age_low', ts('Please enter a positive integer'), 'positiveInteger'); - $form->add('text', 'age_high', ts('Max Age'), array('size' => 6)); + $form->add('number', 'age_high', ts('Max Age'), ['class' => 'four', 'min' => 0]); $form->addRule('age_high', ts('Please enter a positive integer'), 'positiveInteger'); - $form->addDate('age_asof_date', ts('Age as of Date'), FALSE, array('formatType' => 'searchDate')); + $form->add('datepicker', 'age_asof_date', ts('As of'), NULL, FALSE, ['time' => FALSE]); CRM_Core_Form_Date::buildDateRange($form, 'birth_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); @@ -479,16 +559,16 @@ public static function demographics(&$form) { public static function notes(&$form) { $form->add('hidden', 'hidden_notes', 1); - $options = array( + $options = [ 2 => ts('Body Only'), 3 => ts('Subject Only'), 6 => ts('Both'), - ); + ]; $form->addRadio('note_option', '', $options); $form->addElement('text', 'note', ts('Note Text'), CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact', 'sort_name')); - $form->setDefaults(array('note_option' => 6)); + $form->setDefaults(['note_option' => 6]); } /** @@ -498,7 +578,7 @@ public static function notes(&$form) { */ public static function custom(&$form) { $form->add('hidden', 'hidden_custom', 1); - $extends = array_merge(array('Contact', 'Individual', 'Household', 'Organization'), + $extends = array_merge(['Contact', 'Individual', 'Household', 'Organization'], CRM_Contact_BAO_ContactType::subTypes() ); $groupDetails = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, TRUE, diff --git a/CRM/Contact/Form/Search/Custom.php b/CRM/Contact/Form/Search/Custom.php index e82d7ad71f2c..fad711ba9017 100644 --- a/CRM/Contact/Form/Search/Custom.php +++ b/CRM/Contact/Form/Search/Custom.php @@ -1,9 +1,9 @@ _customClass = new $this->_customSearchClass($this->_formValues); + $this->addFormRule(array($this->_customClass, 'formRule'), $this); + // CRM-12747 if (isset($this->_customClass->_permissionedComponent) && !self::isPermissioned($this->_customClass->_permissionedComponent) @@ -91,6 +93,13 @@ public function preProcess() { } } + /** + * Add local and global form rules. + */ + public function addRules() { + $this->addFormRule(array($this->_customClass, 'formRule')); + } + /** * Set the default values of various form elements. * @@ -127,6 +136,7 @@ public function buildQuickForm() { * * @return string */ + /** * @return string */ diff --git a/CRM/Contact/Form/Search/Custom/ActivitySearch.php b/CRM/Contact/Form/Search/Custom/ActivitySearch.php index 72bd86e2582c..30d95f184761 100644 --- a/CRM/Contact/Form/Search/Custom/ActivitySearch.php +++ b/CRM/Contact/Form/Search/Custom/ActivitySearch.php @@ -1,9 +1,9 @@ _formValues = $formValues; + $this->_formValues = self::formatSavedSearchFields($formValues); /** * Define the columns for search result rows */ - $this->_columns = array( + $this->_columns = [ ts('Name') => 'sort_name', ts('Status') => 'activity_status', ts('Activity Type') => 'activity_type', @@ -61,7 +61,7 @@ public function __construct(&$formValues) { ts('Duration') => 'duration', ts('Details') => 'details', ts('Assignee') => 'assignee', - ); + ]; $this->_groupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'activity_status', @@ -107,7 +107,7 @@ public function buildForm(&$form) { ); // Select box for Activity Type - $activityType = array('' => ' - select activity - ') + CRM_Core_PseudoConstant::activityType(); + $activityType = ['' => ' - select activity - '] + CRM_Core_PseudoConstant::activityType(); $form->add('select', 'activity_type_id', ts('Activity Type'), $activityType, @@ -115,7 +115,7 @@ public function buildForm(&$form) { ); // textbox for Activity Status - $activityStatus = array('' => ' - select status - ') + CRM_Core_PseudoConstant::activityStatus(); + $activityStatus = ['' => ' - select status - '] + CRM_Core_PseudoConstant::activityStatus(); $form->add('select', 'activity_status_id', ts('Activity Status'), $activityStatus, @@ -123,8 +123,8 @@ public function buildForm(&$form) { ); // Activity Date range - $form->addDate('start_date', ts('Activity Date From'), FALSE, array('formatType' => 'custom')); - $form->addDate('end_date', ts('...through'), FALSE, array('formatType' => 'custom')); + $form->add('datepicker', 'start_date', ts('Activity Date From'), [], FALSE, ['time' => FALSE]); + $form->add('datepicker', 'end_date', ts('...through'), [], FALSE, ['time' => FALSE]); // Contact Name field $form->add('text', 'sort_name', ts('Contact Name')); @@ -133,7 +133,7 @@ public function buildForm(&$form) { * If you are using the sample template, this array tells the template fields to render * for the search form. */ - $form->assign('elements', array( + $form->assign('elements', [ 'contact_type', 'activity_subject', 'activity_type_id', @@ -141,7 +141,7 @@ public function buildForm(&$form) { 'start_date', 'end_date', 'sort_name', - )); + ]); } /** @@ -259,7 +259,7 @@ public function alterRow(&$row) { */ public function from() { $this->buildACLClause('contact_a'); - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); @@ -296,7 +296,7 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; // add contact name search; search on primary name, source contact, assignee $contactname = $this->_formValues['sort_name']; @@ -328,26 +328,16 @@ public function where($includeContactIDs = FALSE) { $clauses[] = "activity.activity_type_id = {$this->_formValues['activity_type_id']}"; } - $startDate = $this->_formValues['start_date']; - if (!empty($startDate)) { - $startDate .= '00:00:00'; - $startDateFormatted = CRM_Utils_Date::processDate($startDate); - if ($startDateFormatted) { - $clauses[] = "activity.activity_date_time >= $startDateFormatted"; - } + if (!empty($this->_formValues['start_date'])) { + $clauses[] = "activity.activity_date_time >= '{$this->_formValues['start_date']} 00:00:00'"; } - $endDate = $this->_formValues['end_date']; - if (!empty($endDate)) { - $endDate .= '23:59:59'; - $endDateFormatted = CRM_Utils_Date::processDate($endDate); - if ($endDateFormatted) { - $clauses[] = "activity.activity_date_time <= $endDateFormatted"; - } + if (!empty($this->_formValues['end_date'])) { + $clauses[] = "activity.activity_date_time <= '{$this->_formValues['end_date']} 23:59:59'"; } if ($includeContactIDs) { - $contactIDs = array(); + $contactIDs = []; foreach ($this->_formValues as $id => $value) { if ($value && substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX @@ -378,9 +368,7 @@ public function where($includeContactIDs = FALSE) { public function count() { $sql = $this->all(); - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); return $dao->N; } @@ -429,4 +417,28 @@ public function buildACLClause($tableAlias = 'contact') { list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); } + /** + * Format saved search fields for this custom group. + * + * Note this is a function to facilitate the transition to jcalendar for + * saved search groups. In time it can be stripped out again. + * + * @param array $formValues + * + * @return array + */ + public static function formatSavedSearchFields($formValues) { + $dateFields = [ + 'start_date', + 'end_date', + ]; + foreach ($formValues as $element => $value) { + if (in_array($element, $dateFields) && !empty($value)) { + $formValues[$element] = date('Y-m-d', strtotime($value)); + } + } + + return $formValues; + } + } diff --git a/CRM/Contact/Form/Search/Custom/Base.php b/CRM/Contact/Form/Search/Custom/Base.php index 19c1a91a4df4..9baf5429b3ed 100644 --- a/CRM/Contact/Form/Search/Custom/Base.php +++ b/CRM/Contact/Form/Search/Custom/Base.php @@ -1,9 +1,9 @@ $value) { if ($value && substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX @@ -200,17 +200,17 @@ public function addSortOffset(&$sql, $offset, $rowcount, $sort) { * @throws Exception */ public function validateUserSQL(&$sql, $onlyWhere = FALSE) { - $includeStrings = array('contact_a'); - $excludeStrings = array('insert', 'delete', 'update'); + $includeStrings = ['contact_a']; + $excludeStrings = ['insert', 'delete', 'update']; if (!$onlyWhere) { - $includeStrings += array('select', 'from', 'where', 'civicrm_contact'); + $includeStrings += ['select', 'from', 'where', 'civicrm_contact']; } foreach ($includeStrings as $string) { if (stripos($sql, $string) === FALSE) { CRM_Core_Error::fatal(ts('Could not find \'%1\' string in SQL clause.', - array(1 => $string) + [1 => $string] )); } } @@ -218,7 +218,7 @@ public function validateUserSQL(&$sql, $onlyWhere = FALSE) { foreach ($excludeStrings as $string) { if (preg_match('/(\s' . $string . ')|(' . $string . '\s)/i', $sql)) { CRM_Core_Error::fatal(ts('Found illegal \'%1\' string in SQL clause.', - array(1 => $string) + [1 => $string] )); } } @@ -257,4 +257,18 @@ public function setTitle($title) { } } + /** + * Validate form input. + * + * @param array $fields + * @param array $files + * @param CRM_Core_Form $self + * + * @return array + * Input errors from the form. + */ + public function formRule($fields, $files, $self) { + return []; + } + } diff --git a/CRM/Contact/Form/Search/Custom/Basic.php b/CRM/Contact/Form/Search/Custom/Basic.php index f5028b0f2658..63a55860c0d7 100644 --- a/CRM/Contact/Form/Search/Custom/Basic.php +++ b/CRM/Contact/Form/Search/Custom/Basic.php @@ -1,9 +1,9 @@ _columns = array( + $this->_columns = [ '' => 'contact_type', ts('Name') => 'sort_name', ts('Address') => 'street_address', @@ -54,10 +54,10 @@ public function __construct(&$formValues) { ts('Country') => 'country', ts('Email') => 'email', ts('Phone') => 'phone', - ); + ]; $params = CRM_Contact_BAO_Query::convertFormValues($this->_formValues); - $returnProperties = array(); + $returnProperties = []; $returnProperties['contact_sub_type'] = 1; $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, @@ -65,14 +65,13 @@ public function __construct(&$formValues) { ); foreach ($this->_columns as $name => $field) { - if (in_array($field, array( - 'street_address', - 'city', - 'state_province', - 'postal_code', - 'country', - )) && empty($addressOptions[$field]) - ) { + if (in_array($field, [ + 'street_address', + 'city', + 'state_province', + 'postal_code', + 'country', + ]) && empty($addressOptions[$field])) { unset($this->_columns[$name]); continue; } @@ -88,21 +87,21 @@ public function __construct(&$formValues) { * @param CRM_Core_Form $form */ public function buildForm(&$form) { - $contactTypes = array('' => ts('- any contact type -')) + CRM_Contact_BAO_ContactType::getSelectElements(); - $form->add('select', 'contact_type', ts('Find...'), $contactTypes, FALSE, array('class' => 'crm-select2 huge')); + $contactTypes = ['' => ts('- any contact type -')] + CRM_Contact_BAO_ContactType::getSelectElements(); + $form->add('select', 'contact_type', ts('Find...'), $contactTypes, FALSE, ['class' => 'crm-select2 huge']); // add select for groups - $group = array('' => ts('- any group -')) + CRM_Core_PseudoConstant::nestedGroup(); - $form->addElement('select', 'group', ts('in'), $group, array('class' => 'crm-select2 huge')); + $group = ['' => ts('- any group -')] + CRM_Core_PseudoConstant::nestedGroup(); + $form->addElement('select', 'group', ts('in'), $group, ['class' => 'crm-select2 huge']); // add select for categories - $tag = array('' => ts('- any tag -')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); - $form->addElement('select', 'tag', ts('Tagged'), $tag, array('class' => 'crm-select2 huge')); + $tag = ['' => ts('- any tag -')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); + $form->addElement('select', 'tag', ts('Tagged'), $tag, ['class' => 'crm-select2 huge']); // text for sort_name $form->add('text', 'sort_name', ts('Name')); - $form->assign('elements', array('sort_name', 'contact_type', 'group', 'tag')); + $form->assign('elements', ['sort_name', 'contact_type', 'group', 'tag']); } /** diff --git a/CRM/Contact/Form/Search/Custom/ContribSYBNT.php b/CRM/Contact/Form/Search/Custom/ContribSYBNT.php index 5af0eefca4fd..326388909005 100644 --- a/CRM/Contact/Form/Search/Custom/ContribSYBNT.php +++ b/CRM/Contact/Form/Search/Custom/ContribSYBNT.php @@ -1,9 +1,9 @@ _formValues = $formValues; + $this->_formValues = self::formatSavedSearchFields($formValues); $this->_permissionedComponent = 'CiviContribute'; - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Name') => 'display_name', ts('Contribution Count') => 'donation_count', ts('Contribution Amount') => 'donation_amount', - ); + ]; - $this->_amounts = array( + $this->_amounts = [ 'min_amount_1' => ts('Min Amount One'), 'max_amount_1' => ts('Max Amount One'), 'min_amount_2' => ts('Min Amount Two'), 'max_amount_2' => ts('Max Amount Two'), 'exclude_min_amount' => ts('Exclusion Min Amount'), 'exclude_max_amount' => ts('Exclusion Max Amount'), - ); + ]; - $this->_dates = array( + $this->_dates = [ 'start_date_1' => ts('Start Date One'), 'end_date_1' => ts('End Date One'), 'start_date_2' => ts('Start Date Two'), 'end_date_2' => ts('End Date Two'), 'exclude_start_date' => ts('Exclusion Start Date'), 'exclude_end_date' => ts('Exclusion End Date'), - ); + ]; - $this->_checkboxes = array('is_first_amount' => ts('First Donation?')); + $this->_checkboxes = ['is_first_amount' => ts('First Donation?')]; foreach ($this->_amounts as $name => $title) { $this->{$name} = CRM_Utils_Array::value($name, $this->_formValues); @@ -83,7 +83,7 @@ public function __construct(&$formValues) { foreach ($this->_dates as $name => $title) { if (!empty($this->_formValues[$name])) { - $this->{$name} = CRM_Utils_Date::processDate($this->_formValues[$name]); + $this->{$name} = $this->_formValues[$name]; } } } @@ -101,7 +101,7 @@ public function buildForm(&$form) { } foreach ($this->_dates as $name => $title) { - $form->addDate($name, $title, FALSE, array('formatType' => 'custom')); + $form->add('datepicker', $name, $title, [], FALSE, ['time' => FALSE]); } foreach ($this->_checkboxes as $name => $title) { @@ -192,10 +192,8 @@ public function all( "; if ($justIDs) { - CRM_Core_DAO::executeQuery("DROP TEMPORARY TABLE IF EXISTS CustomSearch_SYBNT_temp"); - $query = "CREATE TEMPORARY TABLE CustomSearch_SYBNT_temp AS ({$sql})"; - $dao = CRM_Core_DAO::executeQuery($query); - $sql = "SELECT contact_a.id as contact_id FROM CustomSearch_SYBNT_temp as contact_a"; + $tempTable = CRM_Utils_SQL_TempTable::build()->createWithQuery($sql); + $sql = "SELECT contact_a.id as contact_id FROM {$tempTable->getName()} as contact_a"; } return $sql; } @@ -243,25 +241,25 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; if (!empty($this->start_date_1)) { - $clauses[] = "contrib_1.receive_date >= {$this->start_date_1}"; + $clauses[] = CRM_Core_DAO::composeQuery('contrib_1.receive_date >= %1', [1 => [$this->start_date_1, 'String']]); } if (!empty($this->end_date_1)) { - $clauses[] = "contrib_1.receive_date <= {$this->end_date_1}"; + $clauses[] = CRM_Core_DAO::composeQuery('contrib_1.receive_date <= %1', [1 => [$this->end_date_1, 'String']]); } if (!empty($this->start_date_2) || !empty($this->end_date_2)) { $clauses[] = "contrib_2.is_test = 0"; if (!empty($this->start_date_2)) { - $clauses[] = "contrib_2.receive_date >= {$this->start_date_2}"; + $clauses[] = CRM_Core_DAO::composeQuery('contrib_2.receive_date >= %1', [1 => [$this->start_date_2, 'String']]); } if (!empty($this->end_date_2)) { - $clauses[] = "contrib_2.receive_date <= {$this->end_date_2}"; + $clauses[] = CRM_Core_DAO::composeQuery('contrib_2.receive_date <= %1', [1 => [$this->end_date_2, 'String']]); } } @@ -277,13 +275,13 @@ public function where($includeContactIDs = FALSE) { $sql = "CREATE TEMPORARY TABLE XG_CustomSearch_SYBNT ( contact_id int primary key) ENGINE=HEAP"; CRM_Core_DAO::executeQuery($sql); - $excludeClauses = array(); + $excludeClauses = []; if ($this->exclude_start_date) { - $excludeClauses[] = "c.receive_date >= {$this->exclude_start_date}"; + $excludeClauses[] = CRM_Core_DAO::composeQuery('c.receive_date >= %1', [1 => [$this->exclude_start_date, 'String']]); } if ($this->exclude_end_date) { - $excludeClauses[] = "c.receive_date <= {$this->exclude_end_date}"; + $excludeClauses[] = CRM_Core_DAO::composeQuery('c.receive_date <= %1', [1 => [$this->exclude_end_date, 'String']]); } $excludeClause = NULL; @@ -291,7 +289,7 @@ public function where($includeContactIDs = FALSE) { $excludeClause = ' AND ' . implode(' AND ', $excludeClauses); } - $having = array(); + $having = []; if ($this->exclude_min_amount) { $having[] = "sum(c.total_amount) >= {$this->exclude_min_amount}"; } @@ -317,7 +315,7 @@ public function where($includeContactIDs = FALSE) { $havingClause "; - $dao = CRM_Core_DAO::executeQuery($query); + CRM_Core_DAO::executeQuery($query); } // now ensure we dont consider donors that are not first time @@ -329,7 +327,7 @@ public function where($includeContactIDs = FALSE) { WHERE c.is_test = 0 AND c.receive_date < {$this->start_date_1} "; - $dao = CRM_Core_DAO::executeQuery($query); + CRM_Core_DAO::executeQuery($query); } $clauses[] = " xg.contact_id IS NULL "; @@ -346,7 +344,7 @@ public function where($includeContactIDs = FALSE) { * @return string */ public function having($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; $min = CRM_Utils_Array::value('min_amount', $this->_formValues); if ($min) { $clauses[] = "sum(contrib_1.total_amount) >= $min"; @@ -400,4 +398,32 @@ public function buildACLClause($tableAlias = 'contact') { list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); } + /** + * Format saved search fields for this custom group. + * + * Note this is a function to facilitate the transition to jcalendar for + * saved search groups. In time it can be stripped out again. + * + * @param array $formValues + * + * @return array + */ + public static function formatSavedSearchFields($formValues) { + $dateFields = [ + 'start_date_1', + 'end_date_1', + 'start_date_2', + 'end_date_2', + 'exclude_start_date', + 'exclude_end_date', + ]; + foreach ($formValues as $element => $value) { + if (in_array($element, $dateFields) && !empty($value)) { + $formValues[$element] = date('Y-m-d', strtotime($value)); + } + } + + return $formValues; + } + } diff --git a/CRM/Contact/Form/Search/Custom/ContributionAggregate.php b/CRM/Contact/Form/Search/Custom/ContributionAggregate.php index db14a49ea737..5437e0d2a025 100644 --- a/CRM/Contact/Form/Search/Custom/ContributionAggregate.php +++ b/CRM/Contact/Form/Search/Custom/ContributionAggregate.php @@ -1,9 +1,9 @@ _formValues = $formValues; // Define the columns for search result rows - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Name') => 'sort_name', ts('Contribution Count') => 'donation_count', ts('Contribution Amount') => 'donation_amount', - ); + ]; // define component access permission needed $this->_permissionedComponent = 'CiviContribute'; @@ -63,6 +63,8 @@ public function __construct(&$formValues) { * @param CRM_Core_Form $form */ public function buildForm(&$form) { + $form->addSearchFieldMetadata(['Contribution' => self::getSearchFieldMetadata()]); + $form->addFormFieldsFromMetadata(); /** * You can define a custom title for the search form @@ -83,17 +85,16 @@ public function buildForm(&$form) { ts('...and $') ); $form->addRule('max_amount', ts('Please enter a valid amount (numbers and decimal point only).'), 'money'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_date', 1, '_low', '_high', ts('From:'), FALSE, FALSE); $form->addSelect('financial_type_id', - array('entity' => 'contribution', 'multiple' => 'multiple', 'context' => 'search') + ['entity' => 'contribution', 'multiple' => 'multiple', 'context' => 'search'] ); /** * If you are using the sample template, this array tells the template fields to render * for the search form. */ - $form->assign('elements', array('min_amount', 'max_amount')); + $form->assign('elements', ['min_amount', 'max_amount']); } /** @@ -187,6 +188,20 @@ public function from() { return $from; } + /** + * Get the metadata for fields to be included on the contact search form. + */ + public static function getSearchFieldMetadata() { + $fields = [ + 'receive_date' => ['title' => ''], + ]; + $metadata = civicrm_api3('Contribution', 'getfields', [])['values']; + foreach ($fields as $fieldName => $field) { + $fields[$fieldName] = array_merge(CRM_Utils_Array::value($fieldName, $metadata, []), $field); + } + return $fields; + } + /** * WHERE clause is an array built from any required JOINS plus conditional filters based on search criteria field values. * @@ -195,28 +210,40 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $clauses = array( + $clauses = [ "contrib.contact_id = contact_a.id", "contrib.is_test = 0", - ); + ]; + + foreach ([ + 'receive_date_relative', + 'receive_date_low', + 'receive_date_high', + ] as $dateFieldName) { + $dateParams[$dateFieldName] = CRM_Utils_Array::value( + $dateFieldName, + $this->_formValues + ); + } - $dateParams = array( - 'contribution_date_relative' => $this->_formValues['contribution_date_relative'], - 'contribution_date_low' => $this->_formValues['contribution_date_low'], - 'contribution_date_high' => $this->_formValues['contribution_date_high'], - ); foreach (CRM_Contact_BAO_Query::convertFormValues($dateParams) as $values) { list($name, $op, $value) = $values; if (strstr($name, '_low')) { - $clauses[] = "contrib.receive_date >= " . CRM_Utils_Date::processDate($value); + if (strlen($value) == 10) { + $value .= ' 00:00:00'; + } + $clauses[] = "contrib.receive_date >= '{$value}'"; } else { - $clauses[] = "contrib.receive_date <= " . CRM_Utils_Date::processDate($value); + if (strlen($value) == 10) { + $value .= ' 23:59:59'; + } + $clauses[] = "contrib.receive_date <= '{$value}'"; } } if ($includeContactIDs) { - $contactIDs = array(); + $contactIDs = []; foreach ($this->_formValues as $id => $value) { if ($value && substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX @@ -248,7 +275,7 @@ public function where($includeContactIDs = FALSE) { * @return string */ public function having($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; $min = CRM_Utils_Array::value('min_amount', $this->_formValues); if ($min) { $min = CRM_Utils_Rule::cleanMoney($min); @@ -274,9 +301,7 @@ public function having($includeContactIDs = FALSE) { public function count() { $sql = $this->all(); - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); return $dao->N; } diff --git a/CRM/Contact/Form/Search/Custom/DateAdded.php b/CRM/Contact/Form/Search/Custom/DateAdded.php index 04a392cda569..12b3a03e54ff 100644 --- a/CRM/Contact/Form/Search/Custom/DateAdded.php +++ b/CRM/Contact/Form/Search/Custom/DateAdded.php @@ -1,9 +1,9 @@ _formValues = self::formatSavedSearchFields($formValues); - $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $formValues, array()); - $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $formValues, array()); + $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $formValues, []); + $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $formValues, []); - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Contact Type') => 'contact_type', ts('Name') => 'sort_name', ts('Date Added') => 'date_added', - ); + ]; } /** * @param CRM_Core_Form $form */ public function buildForm(&$form) { - $form->addDate('start_date', ts('Start Date'), FALSE, array('formatType' => 'custom')); - $form->addDate('end_date', ts('End Date'), FALSE, array('formatType' => 'custom')); + $form->add('datepicker', 'start_date', ts('Start Date'), [], FALSE, ['time' => FALSE]); + $form->add('datepicker', 'end_date', ts('End Date'), [], FALSE, ['time' => FALSE]); $groups = CRM_Core_PseudoConstant::nestedGroup(); - $select2style = array( + $select2style = [ 'multiple' => TRUE, 'style' => 'width: 100%; max-width: 60em;', 'class' => 'crm-select2', 'placeholder' => ts('- select -'), - ); + ]; $form->add('select', 'includeGroups', ts('Include Group(s)'), @@ -100,7 +103,7 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('start_date', 'end_date', 'includeGroups', 'excludeGroups')); + $form->assign('elements', ['start_date', 'end_date', 'includeGroups', 'excludeGroups']); } /** @@ -136,9 +139,9 @@ public function all( $includeContactIDs = FALSE, $justIDs = FALSE ) { - $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $this->_formValues, array()); + $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $this->_formValues, []); - $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $this->_formValues, array()); + $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $this->_formValues, []); $this->_allSearch = FALSE; $this->_groups = FALSE; @@ -177,27 +180,24 @@ public function all( */ public function from() { //define table name - $randomNum = md5(uniqid()); - $this->_tableName = "civicrm_temp_custom_{$randomNum}"; + $datesTable = CRM_Utils_SQL_TempTable::build()->setCategory('dates')->setMemory(); + $this->_datesTable = $datesTable->getName(); + $xgTable = CRM_Utils_SQL_TempTable::build()->setCategory('xg')->setMemory(); + $this->_xgTable = $xgTable->getName(); + $igTable = CRM_Utils_SQL_TempTable::build()->setCategory('ig')->setMemory(); + $this->_igTable = $igTable->getName(); //grab the contacts added in the date range first - $sql = "CREATE TEMPORARY TABLE dates_{$this->_tableName} ( id int primary key, date_added date ) ENGINE=HEAP"; - if ($this->_debug > 0) { - print "-- Date range query:
    ";
    -      print "$sql;";
    -      print "
    "; - } - CRM_Core_DAO::executeQuery($sql); + $datesTable->createWithColumns('id int primary key, date_added date'); - $startDate = CRM_Utils_Date::mysqlToIso(CRM_Utils_Date::processDate($this->_formValues['start_date'])); + $startDate = !empty($this->_formValues['start_date']) ? $this->_formValues['start_date'] : date('Y-m-d'); $endDateFix = NULL; if (!empty($this->_formValues['end_date'])) { - $endDate = CRM_Utils_Date::mysqlToIso(CRM_Utils_Date::processDate($this->_formValues['end_date'])); - # tack 11:59pm on to make search inclusive of the end date - $endDateFix = "AND date_added <= '" . substr($endDate, 0, 10) . " 23:59:00'"; + // tack 11:59pm on to make search inclusive of the end date + $endDateFix = "AND date_added <= '{$this->_formValues['end_date']} 23:59:00'"; } - $dateRange = "INSERT INTO dates_{$this->_tableName} ( id, date_added ) + $dateRange = "INSERT INTO {$this->_datesTable} ( id, date_added ) SELECT civicrm_contact.id, min(civicrm_log.modified_date) AS date_added @@ -208,22 +208,16 @@ public function from() { GROUP BY civicrm_contact.id HAVING - date_added >= '$startDate' + date_added >= '$startDate 00:00:00' $endDateFix"; - if ($this->_debug > 0) { - print "-- Date range query:
    ";
    -      print "$dateRange;";
    -      print "
    "; - } - - CRM_Core_DAO::executeQuery($dateRange, CRM_Core_DAO::$_nullArray); + CRM_Core_DAO::executeQuery($dateRange); // Only include groups in the search query of one or more Include OR Exclude groups has been selected. // CRM-6356 if ($this->_groups) { //block for Group search - $smartGroup = array(); + $smartGroup = []; $group = new CRM_Contact_DAO_Group(); $group->is_active = 1; $group->find(); @@ -249,22 +243,20 @@ public function from() { $xGroups = 0; } - $sql = "DROP TEMPORARY TABLE IF EXISTS Xg_{$this->_tableName}"; - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); - $sql = "CREATE TEMPORARY TABLE Xg_{$this->_tableName} ( contact_id int primary key) ENGINE=HEAP"; - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); + $xgTable->drop(); + $xgTable->createWithColumns('contact_id int primary key'); //used only when exclude group is selected if ($xGroups != 0) { - $excludeGroup = "INSERT INTO Xg_{$this->_tableName} ( contact_id ) + $excludeGroup = "INSERT INTO {$this->_xgTable} ( contact_id ) SELECT DISTINCT civicrm_group_contact.contact_id - FROM civicrm_group_contact, dates_{$this->_tableName} AS d + FROM civicrm_group_contact, {$this->_datesTable} AS d WHERE d.id = civicrm_group_contact.contact_id AND civicrm_group_contact.status = 'Added' AND civicrm_group_contact.group_id IN( {$xGroups})"; - CRM_Core_DAO::executeQuery($excludeGroup, CRM_Core_DAO::$_nullArray); + CRM_Core_DAO::executeQuery($excludeGroup); //search for smart group contacts foreach ($this->_excludeGroups as $keys => $values) { @@ -277,31 +269,21 @@ public function from() { SELECT contact_id FROM civicrm_group_contact WHERE civicrm_group_contact.group_id = {$values} AND civicrm_group_contact.status = 'Removed')"; - $smartGroupQuery = " INSERT IGNORE INTO Xg_{$this->_tableName}(contact_id) $smartSql"; + $smartGroupQuery = " INSERT IGNORE INTO {$this->_xgTable}(contact_id) $smartSql"; - CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray); + CRM_Core_DAO::executeQuery($smartGroupQuery); } } } - $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}"; - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); - $sql = "CREATE TEMPORARY TABLE Ig_{$this->_tableName} - ( id int PRIMARY KEY AUTO_INCREMENT, + $igTable->drop(); + $igTable->createWithColumns('id int PRIMARY KEY AUTO_INCREMENT, contact_id int, - group_names varchar(64)) ENGINE=HEAP"; + group_names varchar(64)'); - if ($this->_debug > 0) { - print "-- Include groups query:
    ";
    -        print "$sql;";
    -        print "
    "; - } - - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); - - $includeGroup = "INSERT INTO Ig_{$this->_tableName} (contact_id, group_names) + $includeGroup = "INSERT INTO {$this->_igTable} (contact_id, group_names) SELECT d.id as contact_id, civicrm_group.name as group_name - FROM dates_{$this->_tableName} AS d + FROM {$this->_datesTable} AS d INNER JOIN civicrm_group_contact ON civicrm_group_contact.contact_id = d.id LEFT JOIN civicrm_group @@ -309,8 +291,8 @@ public function from() { //used only when exclude group is selected if ($xGroups != 0) { - $includeGroup .= " LEFT JOIN Xg_{$this->_tableName} - ON d.id = Xg_{$this->_tableName}.contact_id"; + $includeGroup .= " LEFT JOIN {$this->_xgTable} + ON d.id = {$this->_xgTable}.contact_id"; } $includeGroup .= " WHERE civicrm_group_contact.status = 'Added' AND @@ -318,16 +300,10 @@ public function from() { //used only when exclude group is selected if ($xGroups != 0) { - $includeGroup .= " AND Xg_{$this->_tableName}.contact_id IS null"; + $includeGroup .= " AND {$this->_xgTable}.contact_id IS null"; } - if ($this->_debug > 0) { - print "-- Include groups query:
    ";
    -        print "$includeGroup;";
    -        print "
    "; - } - - CRM_Core_DAO::executeQuery($includeGroup, CRM_Core_DAO::$_nullArray); + CRM_Core_DAO::executeQuery($includeGroup); //search for smart group contacts foreach ($this->_includeGroups as $keys => $values) { @@ -339,7 +315,7 @@ public function from() { $smartSql .= " AND contact_a.id IN ( SELECT id AS contact_id - FROM dates_{$this->_tableName} )"; + FROM {$this->_datesTable} )"; $smartSql .= " AND contact_a.id NOT IN ( SELECT contact_id FROM civicrm_group_contact @@ -347,30 +323,20 @@ public function from() { //used only when exclude group is selected if ($xGroups != 0) { - $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM Xg_{$this->_tableName})"; + $smartSql .= " AND contact_a.id NOT IN (SELECT contact_id FROM {$this->_xgTable})"; } $smartGroupQuery = " INSERT IGNORE INTO - Ig_{$this->_tableName}(contact_id) + {$this->_igTable}(contact_id) $smartSql"; - CRM_Core_DAO::executeQuery($smartGroupQuery, CRM_Core_DAO::$_nullArray); - if ($this->_debug > 0) { - print "-- Smart group query:
    ";
    -            print "$smartGroupQuery;";
    -            print "
    "; - } - $insertGroupNameQuery = "UPDATE IGNORE Ig_{$this->_tableName} + CRM_Core_DAO::executeQuery($smartGroupQuery); + $insertGroupNameQuery = "UPDATE IGNORE {$this->_igTable} SET group_names = (SELECT title FROM civicrm_group WHERE civicrm_group.id = $values) - WHERE Ig_{$this->_tableName}.contact_id IS NOT NULL - AND Ig_{$this->_tableName}.group_names IS NULL"; - CRM_Core_DAO::executeQuery($insertGroupNameQuery, CRM_Core_DAO::$_nullArray); - if ($this->_debug > 0) { - print "-- Smart group query:
    ";
    -            print "$insertGroupNameQuery;";
    -            print "
    "; - } + WHERE {$this->_igTable}.contact_id IS NOT NULL + AND {$this->_igTable}.group_names IS NULL"; + CRM_Core_DAO::executeQuery($insertGroupNameQuery); } } } @@ -380,12 +346,12 @@ public function from() { /* We need to join to this again to get the date_added value */ - $from .= " INNER JOIN dates_{$this->_tableName} d ON (contact_a.id = d.id) {$this->_aclFrom}"; + $from .= " INNER JOIN {$this->_datesTable} d ON (contact_a.id = d.id) {$this->_aclFrom}"; // Only include groups in the search query of one or more Include OR Exclude groups has been selected. // CRM-6356 if ($this->_groups) { - $from .= " INNER JOIN Ig_{$this->_tableName} temptable1 ON (contact_a.id = temptable1.contact_id)"; + $from .= " INNER JOIN {$this->_igTable} temptable1 ON (contact_a.id = temptable1.contact_id)"; } return $from; @@ -429,22 +395,20 @@ public function setTitle($title) { public function count() { $sql = $this->all(); - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); return $dao->N; } public function __destruct() { //drop the temp. tables if they exist - if (!empty($this->_includeGroups)) { - $sql = "DROP TEMPORARY TABLE IF EXISTS Ig_{$this->_tableName}"; - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); + if ($this->_igTable && !empty($this->_includeGroups)) { + $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_igTable}"; + CRM_Core_DAO::executeQuery($sql); } - if (!empty($this->_excludeGroups)) { - $sql = "DROP TEMPORARY TABLE IF EXISTS Xg_{$this->_tableName}"; - CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); + if ($this->_xgTable && !empty($this->_excludeGroups)) { + $sql = "DROP TEMPORARY TABLE IF EXISTS {$this->_xgTable}"; + CRM_Core_DAO::executeQuery($sql); } } @@ -455,4 +419,27 @@ public function buildACLClause($tableAlias = 'contact') { list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); } + /** + * Format saved search fields for this custom group. + * + * Note this is a function to facilitate the transition to jcalendar for + * saved search groups. In time it can be stripped out again. + * + * @param array $formValues + * + * @return array + */ + public static function formatSavedSearchFields($formValues) { + $dateFields = [ + 'start_date', + 'end_date', + ]; + foreach ($formValues as $element => $value) { + if (in_array($element, $dateFields) && !empty($value)) { + $formValues[$element] = date('Y-m-d', strtotime($value)); + } + } + return $formValues; + } + } diff --git a/CRM/Contact/Form/Search/Custom/EventAggregate.php b/CRM/Contact/Form/Search/Custom/EventAggregate.php index 2947696c3aa6..e3b36fc95dc7 100644 --- a/CRM/Contact/Form/Search/Custom/EventAggregate.php +++ b/CRM/Contact/Form/Search/Custom/EventAggregate.php @@ -1,9 +1,9 @@ _formValues = $formValues; - $this->_permissionedComponent = array('CiviContribute', 'CiviEvent'); + $this->_formValues = self::formatSavedSearchFields($formValues); + $this->_permissionedComponent = ['CiviContribute', 'CiviEvent']; /** * Define the columns for search result rows */ - $this->_columns = array( + $this->_columns = [ ts('Event') => 'event_name', ts('Type') => 'event_type', ts('Number of
    Participant') => 'participant_count', @@ -57,7 +57,7 @@ public function __construct(&$formValues) { ts('Fee') => 'fee', ts('Net Payment') => 'net_payment', ts('Participant') => 'participant', - ); + ]; } /** @@ -83,23 +83,23 @@ public function buildForm(&$form) { $form->addElement('checkbox', "event_type_id[$eventId]", 'Event Type', $eventName); } $events = CRM_Event_BAO_Event::getEvents(1); - $form->add('select', 'event_id', ts('Event Name'), array('' => ts('- select -')) + $events); + $form->add('select', 'event_id', ts('Event Name'), ['' => ts('- select -')] + $events); - $form->addDate('start_date', ts('Payments Date From'), FALSE, array('formatType' => 'custom')); - $form->addDate('end_date', ts('...through'), FALSE, array('formatType' => 'custom')); + $form->add('datepicker', 'start_date', ts('Payments Date From'), [], FALSE, ['time' => FALSE]); + $form->add('datepicker', 'end_date', ts('...through'), [], FALSE, ['time' => FALSE]); /** * If you are using the sample template, this array tells the template fields to render * for the search form. */ - $form->assign('elements', array( - 'paid_online', - 'start_date', - 'end_date', - 'show_payees', - 'event_type_id', - 'event_id', - )); + $form->assign('elements', [ + 'paid_online', + 'start_date', + 'end_date', + 'show_payees', + 'event_type_id', + 'event_id', + ]); } /** @@ -226,7 +226,7 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; $clauses[] = "civicrm_participant.status_id in ( 1 )"; $clauses[] = "civicrm_contribution.is_test = 0"; @@ -237,14 +237,13 @@ public function where($includeContactIDs = FALSE) { $clauses[] = "civicrm_contribution.payment_instrument_id <> 0"; } - $startDate = CRM_Utils_Date::processDate($this->_formValues['start_date']); - if ($startDate) { - $clauses[] = "civicrm_contribution.receive_date >= $startDate"; + // As we only allow date to be submitted we need to set default times so midnight for start time and just before midnight for end time. + if ($this->_formValues['start_date']) { + $clauses[] = "civicrm_contribution.receive_date >= '{$this->_formValues['start_date']} 00:00:00'"; } - $endDate = CRM_Utils_Date::processDate($this->_formValues['end_date']); - if ($endDate) { - $clauses[] = "civicrm_contribution.receive_date <= {$endDate}235959"; + if ($this->_formValues['end_date']) { + $clauses[] = "civicrm_contribution.receive_date <= '{$this->_formValues['end_date']} 23:59:59'"; } if (!empty($this->_formValues['event_id'])) { @@ -252,7 +251,7 @@ public function where($includeContactIDs = FALSE) { } if ($includeContactIDs) { - $contactIDs = array(); + $contactIDs = []; foreach ($this->_formValues as $id => $value) { if ($value && substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX @@ -277,8 +276,8 @@ public function where($includeContactIDs = FALSE) { return implode(' AND ', $clauses); } - /* This function does a query to get totals for some of the search result columns and returns a totals array. */ + /** * @return array */ @@ -307,10 +306,8 @@ public function summary() { WHERE $where "; - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); - $totals = array(); + $dao = CRM_Core_DAO::executeQuery($sql); + $totals = []; while ($dao->fetch()) { $totals['payment_amount'] = $dao->payment_amount; $totals['fee'] = $dao->fee; @@ -330,9 +327,7 @@ public function summary() { public function count() { $sql = $this->all(); - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); return $dao->N; } @@ -374,4 +369,28 @@ public function buildACLClause($tableAlias = 'contact') { list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); } + /** + * Format saved search fields for this custom group. + * + * Note this is a function to facilitate the transition to jcalendar for + * saved search groups. In time it can be stripped out again. + * + * @param array $formValues + * + * @return array + */ + public static function formatSavedSearchFields($formValues) { + $dateFields = [ + 'start_date', + 'end_date', + ]; + foreach ($formValues as $element => $value) { + if (in_array($element, $dateFields) && !empty($value)) { + $formValues[$element] = date('Y-m-d', strtotime($value)); + } + } + + return $formValues; + } + } diff --git a/CRM/Contact/Form/Search/Custom/FullText.php b/CRM/Contact/Form/Search/Custom/FullText.php index f52c1bde2734..46050f87819f 100644 --- a/CRM/Contact/Form/Search/Custom/FullText.php +++ b/CRM/Contact/Form/Search/Custom/FullText.php @@ -1,9 +1,9 @@ $limit, 1 => $offset) + * Limit clause. + * + * NULL if no limit; or array(0 => $limit, 1 => $offset). + * + * @var array|null */ protected $_limitClause = NULL; /** - * @var array|null NULL if no limit; or array(0 => $limit, 1 => $offset) + * Limit row clause. + * + * NULL if no limit; or array(0 => $limit, 1 => $offset) + * + * @var array|null */ protected $_limitRowClause = NULL; /** - * @var array|null NULL if no limit; or array(0 => $limit, 1 => $offset) + * Limit detail clause. + * + * NULL if no limit; or array(0 => $limit, 1 => $offset). + * + * @var array|null */ protected $_limitDetailClause = NULL; protected $_limitNumber = 10; - protected $_limitNumberPlus1 = 11; // this should be one more than self::LIMIT - protected $_foundRows = array(); + /** + * This should be one more than self::LIMIT. + * + * @var int + */ + protected $_limitNumberPlus1 = 11; + + protected $_foundRows = []; /** * Class constructor. @@ -79,14 +97,14 @@ class CRM_Contact_Form_Search_Custom_FullText extends CRM_Contact_Form_Search_Cu * @param array $formValues */ public function __construct(&$formValues) { - $this->_partialQueries = array( + $this->_partialQueries = [ new CRM_Contact_Form_Search_Custom_FullText_Contact(), new CRM_Contact_Form_Search_Custom_FullText_Activity(), new CRM_Contact_Form_Search_Custom_FullText_Case(), new CRM_Contact_Form_Search_Custom_FullText_Contribution(), new CRM_Contact_Form_Search_Custom_FullText_Participant(), new CRM_Contact_Form_Search_Custom_FullText_Membership(), - ); + ]; $formValues['table'] = $this->getFieldValue($formValues, 'table', 'String'); $this->_table = $formValues['table']; @@ -95,8 +113,8 @@ public function __construct(&$formValues) { $this->_text = $formValues['text']; if (!$this->_table) { - $this->_limitClause = array($this->_limitNumberPlus1, NULL); - $this->_limitRowClause = $this->_limitDetailClause = array($this->_limitNumber, NULL); + $this->_limitClause = [$this->_limitNumberPlus1, NULL]; + $this->_limitRowClause = $this->_limitDetailClause = [$this->_limitNumber, NULL]; } else { // when there is table specified, we would like to use the pager. But since @@ -107,8 +125,8 @@ public function __construct(&$formValues) { $pageId = CRM_Utils_Array::value('crmPID', $_REQUEST, 1); $offset = ($pageId - 1) * $rowCount; $this->_limitClause = NULL; - $this->_limitRowClause = array($rowCount, NULL); - $this->_limitDetailClause = array($rowCount, $offset); + $this->_limitRowClause = [$rowCount, NULL]; + $this->_limitDetailClause = [$rowCount, $offset]; } $this->_formValues = $formValues; @@ -147,10 +165,10 @@ public function initialize() { } public function buildTempTable() { - $randomNum = md5(uniqid()); - $this->_tableName = "civicrm_temp_custom_details_{$randomNum}"; + $table = CRM_Utils_SQL_TempTable::build()->setCategory('custom')->setMemory(); + $this->_tableName = $table->getName(); - $this->_tableFields = array( + $this->_tableFields = [ 'id' => 'int unsigned NOT NULL AUTO_INCREMENT', 'table_name' => 'varchar(16)', 'contact_id' => 'int unsigned', @@ -196,11 +214,11 @@ public function buildTempTable() { 'membership_status' => 'varchar(255)', // We may have multiple files to list on one record. // The temporary-table approach can't store full details for all of them - 'file_ids' => 'varchar(255)', // comma-separate id listing - ); + // comma-separate id listing + 'file_ids' => 'varchar(255)', + ]; $sql = " -CREATE TEMPORARY TABLE {$this->_tableName} ( "; foreach ($this->_tableFields as $name => $desc) { @@ -209,21 +227,19 @@ public function buildTempTable() { $sql .= " PRIMARY KEY ( id ) -) ENGINE=HEAP DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci "; - CRM_Core_DAO::executeQuery($sql); + $table->createWithColumns($sql); - $this->_entityIDTableName = "civicrm_temp_custom_entityID_{$randomNum}"; + $entityIdTable = CRM_Utils_SQL_TempTable::build()->setCategory('custom')->setMemory(); + $this->_entityIDTableName = $entityIdTable->getName(); $sql = " -CREATE TEMPORARY TABLE {$this->_entityIDTableName} ( id int unsigned NOT NULL AUTO_INCREMENT, entity_id int unsigned NOT NULL, UNIQUE INDEX unique_entity_id ( entity_id ), PRIMARY KEY ( id ) -) ENGINE=HEAP DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci "; - CRM_Core_DAO::executeQuery($sql); + $entityIdTable->createWithColumns($sql); if (!empty($this->_formValues['is_unit_test'])) { $this->_tableNameForTest = $this->_tableName; @@ -258,7 +274,7 @@ public function filterACLContacts() { CRM_Contact_BAO_Contact_Permission::cache($contactID); - $params = array(1 => array($contactID, 'Integer')); + $params = [1 => [$contactID, 'Integer']]; $sql = " DELETE t.* @@ -304,7 +320,7 @@ public function buildForm(&$form) { ); // also add a select box to allow the search to be constrained - $tables = array('' => ts('All tables')); + $tables = ['' => ts('All tables')]; foreach ($this->_partialQueries as $partialQuery) { /** @var $partialQuery CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery */ if ($partialQuery->isActive()) { @@ -321,7 +337,7 @@ public function buildForm(&$form) { // set form defaults if (!empty($form->_formValues)) { - $defaults = array(); + $defaults = []; if (isset($form->_formValues['text'])) { $defaults['text'] = $form->_formValues['text']; @@ -348,10 +364,10 @@ public function buildForm(&$form) { * @return array */ public function &columns() { - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Name') => 'sort_name', - ); + ]; return $this->_columns; } @@ -362,10 +378,10 @@ public function &columns() { public function summary() { $this->initialize(); - $summary = array(); + $summary = []; foreach ($this->_partialQueries as $partialQuery) { /** @var $partialQuery CRM_Contact_Form_Search_Custom_FullText_AbstractPartialQuery */ - $summary[$partialQuery->getName()] = array(); + $summary[$partialQuery->getName()] = []; } // now iterate through the table and add entries to the relevant section @@ -378,7 +394,7 @@ public function summary() { $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE); $roleIds = CRM_Event_PseudoConstant::participantRole(); while ($dao->fetch()) { - $row = array(); + $row = []; foreach ($this->_tableFields as $name => $dontCare) { if ($name != 'activity_type_id') { $row[$name] = $dao->$name; @@ -389,7 +405,7 @@ public function summary() { } if (isset($row['participant_role'])) { $participantRole = explode(CRM_Core_DAO::VALUE_SEPARATOR, $row['participant_role']); - $viewRoles = array(); + $viewRoles = []; foreach ($participantRole as $v) { $viewRoles[] = $roleIds[$v]; } @@ -409,7 +425,7 @@ public function summary() { $summary[$dao->table_name][] = $row; } - $summary['Count'] = array(); + $summary['Count'] = []; foreach (array_keys($summary) as $table) { $summary['Count'][$table] = CRM_Utils_Array::value($table, $this->_foundRows); if ($summary['Count'][$table] >= self::LIMIT) { @@ -513,7 +529,7 @@ public function templateFile() { * @return array */ public function setDefaultValues() { - return array(); + return []; } /** diff --git a/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php b/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php index f5f5ddc2bea9..7f3582923870 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php +++ b/CRM/Contact/Form/Search/Custom/FullText/AbstractPartialQuery.php @@ -1,9 +1,9 @@ $limit, 1 => $offset) - * @param array|NULL $detailLimit final limit (applied when building $detailTable) + * @param array|null $detailLimit final limit (applied when building $detailTable) * NULL if no limit; or array(0 => $limit, 1 => $offset) * @return array * keys: match-descriptor * - count: int */ - public abstract function fillTempTable($queryText, $entityIDTableName, $detailTable, $queryLimit, $detailLimit); + abstract public function fillTempTable($queryText, $entityIDTableName, $detailTable, $queryLimit, $detailLimit); /** * @return bool @@ -121,10 +121,10 @@ public function fillCustomInfo(&$tables, $extends) { $dao = CRM_Core_DAO::executeQuery($sql); while ($dao->fetch()) { if (!array_key_exists($dao->table_name, $tables)) { - $tables[$dao->table_name] = array( + $tables[$dao->table_name] = [ 'id' => 'entity_id', - 'fields' => array(), - ); + 'fields' => [], + ]; } $tables[$dao->table_name]['fields'][$dao->column_name] = NULL; } @@ -175,15 +175,15 @@ public function runQueries($queryText, &$tables, $entityIDTableName, $limit) { continue; } - $query = $tableValues + array( + $query = $tableValues + [ 'text' => CRM_Utils_QueryFormatter::singleton() - ->format($queryText, CRM_Utils_QueryFormatter::LANG_SOLR), - ); + ->format($queryText, CRM_Utils_QueryFormatter::LANG_SOLR), + ]; list($intLimit, $intOffset) = $this->parseLimitOffset($limit); $files = $searcher->search($query, $intLimit, $intOffset); - $matches = array(); + $matches = []; foreach ($files as $file) { - $matches[] = array('entity_id' => $file['xparent_id']); + $matches[] = ['entity_id' => $file['xparent_id']]; } if ($matches) { $insertSql = CRM_Utils_SQL_Insert::into($entityIDTableName)->usingReplace()->rows($matches)->toSQL(); @@ -191,8 +191,10 @@ public function runQueries($queryText, &$tables, $entityIDTableName, $limit) { } } else { - $fullTextFields = array(); // array (string $sqlColumnName) - $clauses = array(); // array (string $sqlExpression) + // array (string $sqlColumnName) + $fullTextFields = []; + // array (string $sqlExpression) + $clauses = []; foreach ($tableValues['fields'] as $fieldName => $fieldType) { if ($fieldType == 'Int') { @@ -242,10 +244,10 @@ public function runQueries($queryText, &$tables, $entityIDTableName, $limit) { } } - return array( + return [ 'count' => CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM {$entityIDTableName}"), 'files' => $files, - ); + ]; } /** @@ -277,16 +279,16 @@ public function moveFileIDs($toTable, $parentIdColumn, $files) { return; } - $filesIndex = CRM_Utils_Array::index(array('xparent_id', 'file_id'), $files); + $filesIndex = CRM_Utils_Array::index(['xparent_id', 'file_id'], $files); // ex: $filesIndex[$xparent_id][$file_id] = array(...the file record...); $dao = CRM_Core_DAO::executeQuery(" SELECT distinct {$parentIdColumn} FROM {$toTable} WHERE table_name = %1 - ", array( - 1 => array($this->getName(), 'String'), - )); + ", [ + 1 => [$this->getName(), 'String'], + ]); while ($dao->fetch()) { if (empty($filesIndex[$dao->{$parentIdColumn}])) { continue; @@ -295,11 +297,11 @@ public function moveFileIDs($toTable, $parentIdColumn, $files) { CRM_Core_DAO::executeQuery("UPDATE {$toTable} SET file_ids = %1 WHERE table_name = %2 AND {$parentIdColumn} = %3 - ", array( - 1 => array(implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'), - 2 => array($this->getName(), 'String'), - 3 => array($dao->{$parentIdColumn}, 'Int'), - )); + ", [ + 1 => [implode(',', array_keys($filesIndex[$dao->{$parentIdColumn}])), 'String'], + 2 => [$this->getName(), 'String'], + 3 => [$dao->{$parentIdColumn}, 'Int'], + ]); } } @@ -338,7 +340,7 @@ public function parseLimitOffset($limit) { if (!$intOffset) { $intOffset = 0; } - return array($intLimit, $intOffset); + return [$intLimit, $intOffset]; } } diff --git a/CRM/Contact/Form/Search/Custom/FullText/Activity.php b/CRM/Contact/Form/Search/Custom/FullText/Activity.php index deb1ff7e4087..9af565f7bb42 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Activity.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Activity.php @@ -1,9 +1,9 @@ matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)}) + ({$this->matchText('civicrm_contact c', ['sort_name', 'display_name', 'nick_name'], $queryText)}) OR ({$this->matchText('civicrm_email e', 'email', $queryText)} AND ca.activity_type_id = ov.value AND ov.name IN ('Inbound Email', 'Email') ) ) @@ -105,20 +105,20 @@ public function prepareQueries($queryText, $entityIDTableName) { $contactSQL[] = " SELECT distinct ca.id FROM civicrm_activity ca -WHERE ({$this->matchText('civicrm_activity ca', array('subject', 'details'), $queryText)}) +WHERE ({$this->matchText('civicrm_activity ca', ['subject', 'details'], $queryText)}) AND (ca.is_deleted = 0 OR ca.is_deleted IS NULL) "; - $final = array(); + $final = []; - $tables = array( - 'civicrm_activity' => array('fields' => array()), - 'file' => array( + $tables = [ + 'civicrm_activity' => ['fields' => []], + 'file' => [ 'xparent_table' => 'civicrm_activity', - ), + ], 'sql' => $contactSQL, 'final' => $final, - ); + ]; $this->fillCustomInfo($tables, "( 'Activity' )"); return $tables;; diff --git a/CRM/Contact/Form/Search/Custom/FullText/Case.php b/CRM/Contact/Form/Search/Custom/FullText/Case.php index dc586169739f..bf3f7eede706 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Case.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Case.php @@ -1,9 +1,9 @@ matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)}) +WHERE ({$this->matchText('civicrm_contact c', ['sort_name', 'display_name', 'nick_name'], $queryText)}) AND (cc.is_deleted = 0 OR cc.is_deleted IS NULL) "; @@ -106,13 +106,13 @@ public function prepareQueries($queryText, $entityIDTableName) { GROUP BY et.entity_id "; - $tables = array( - 'civicrm_case' => array('fields' => array()), - 'file' => array( + $tables = [ + 'civicrm_case' => ['fields' => []], + 'file' => [ 'xparent_table' => 'civicrm_case', - ), + ], 'sql' => $contactSQL, - ); + ]; return $tables; } diff --git a/CRM/Contact/Form/Search/Custom/FullText/Contact.php b/CRM/Contact/Form/Search/Custom/FullText/Contact.php index 0849903b04f7..4930e99cd160 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Contact.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Contact.php @@ -1,9 +1,9 @@ array( + $tables = [ + 'civicrm_contact' => [ 'id' => 'id', - 'fields' => array( + 'fields' => [ 'sort_name' => NULL, 'nick_name' => NULL, 'display_name' => NULL, - ), - ), - 'civicrm_address' => array( + ], + ], + 'civicrm_address' => [ 'id' => 'contact_id', - 'fields' => array( + 'fields' => [ 'street_address' => NULL, 'city' => NULL, 'postal_code' => NULL, - ), - ), - 'civicrm_email' => array( + ], + ], + 'civicrm_email' => [ 'id' => 'contact_id', - 'fields' => array('email' => NULL), - ), - 'civicrm_phone' => array( + 'fields' => ['email' => NULL], + ], + 'civicrm_phone' => [ 'id' => 'contact_id', - 'fields' => array('phone' => NULL), - ), - 'civicrm_note' => array( + 'fields' => ['phone' => NULL], + ], + 'civicrm_note' => [ 'id' => 'entity_id', 'entity_table' => 'civicrm_contact', - 'fields' => array( + 'fields' => [ 'subject' => NULL, 'note' => NULL, - ), - ), - 'file' => array( + ], + ], + 'file' => [ 'xparent_table' => 'civicrm_contact', - ), + ], 'sql' => $contactSQL, 'final' => $final, - ); + ]; // get the custom data info $this->fillCustomInfo($tables, diff --git a/CRM/Contact/Form/Search/Custom/FullText/Contribution.php b/CRM/Contact/Form/Search/Custom/FullText/Contribution.php index 7553ed395199..293b728a6186 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Contribution.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Contribution.php @@ -1,9 +1,9 @@ matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)}) +WHERE ({$this->matchText('civicrm_contact c', ['sort_name', 'display_name', 'nick_name'], $queryText)}) "; - $tables = array( - 'civicrm_contribution' => array( + $tables = [ + 'civicrm_contribution' => [ 'id' => 'id', - 'fields' => array( + 'fields' => [ 'source' => NULL, 'amount_level' => NULL, 'trxn_Id' => NULL, 'invoice_id' => NULL, - 'check_number' => 'Int', // Odd: This is really a VARCHAR, so why are we searching like an INT? + // Odd: This is really a VARCHAR, so why are we searching like an INT? + 'check_number' => 'Int', 'total_amount' => 'Int', - ), - ), - 'file' => array( + ], + ], + 'file' => [ 'xparent_table' => 'civicrm_contribution', - ), + ], 'sql' => $contactSQL, - 'civicrm_note' => array( + 'civicrm_note' => [ 'id' => 'entity_id', 'entity_table' => 'civicrm_contribution', - 'fields' => array( + 'fields' => [ 'subject' => NULL, 'note' => NULL, - ), - ), - ); + ], + ], + ]; // get the custom data info $this->fillCustomInfo($tables, "( 'Contribution' )"); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Membership.php b/CRM/Contact/Form/Search/Custom/FullText/Membership.php index f1666bb5d7bb..f81694b6cf50 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Membership.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Membership.php @@ -1,9 +1,9 @@ matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)}) +WHERE ({$this->matchText('civicrm_contact c', ['sort_name', 'display_name', 'nick_name'], $queryText)}) "; - $tables = array( - 'civicrm_membership' => array( + $tables = [ + 'civicrm_membership' => [ 'id' => 'id', - 'fields' => array('source' => NULL), - ), - 'file' => array( + 'fields' => ['source' => NULL], + ], + 'file' => [ 'xparent_table' => 'civicrm_membership', - ), + ], 'sql' => $contactSQL, - ); + ]; // get the custom data info $this->fillCustomInfo($tables, "( 'Membership' )"); diff --git a/CRM/Contact/Form/Search/Custom/FullText/Participant.php b/CRM/Contact/Form/Search/Custom/FullText/Participant.php index d73a1d1402f0..20bbbd3e4deb 100644 --- a/CRM/Contact/Form/Search/Custom/FullText/Participant.php +++ b/CRM/Contact/Form/Search/Custom/FullText/Participant.php @@ -1,9 +1,9 @@ matchText('civicrm_contact c', array('sort_name', 'display_name', 'nick_name'), $queryText)}) +WHERE ({$this->matchText('civicrm_contact c', ['sort_name', 'display_name', 'nick_name'], $queryText)}) "; - $tables = array( - 'civicrm_participant' => array( + $tables = [ + 'civicrm_participant' => [ 'id' => 'id', - 'fields' => array( + 'fields' => [ 'source' => NULL, 'fee_level' => NULL, 'fee_amount' => 'Int', - ), - ), - 'file' => array( + ], + ], + 'file' => [ 'xparent_table' => 'civicrm_participant', - ), + ], 'sql' => $contactSQL, - 'civicrm_note' => array( + 'civicrm_note' => [ 'id' => 'entity_id', 'entity_table' => 'civicrm_participant', - 'fields' => array( + 'fields' => [ 'subject' => NULL, 'note' => NULL, - ), - ), - ); + ], + ], + ]; // get the custom data info $this->fillCustomInfo($tables, "( 'Participant' )"); diff --git a/CRM/Contact/Form/Search/Custom/Group.php b/CRM/Contact/Form/Search/Custom/Group.php index 9637fb606699..92c4ddd71b96 100644 --- a/CRM/Contact/Form/Search/Custom/Group.php +++ b/CRM/Contact/Form/Search/Custom/Group.php @@ -1,9 +1,9 @@ _formValues = $formValues; - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Contact Type') => 'contact_type', ts('Name') => 'sort_name', ts('Group Name') => 'gname', ts('Tag Name') => 'tname', - ); + ]; - $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $this->_formValues, array()); - $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $this->_formValues, array()); - $this->_includeTags = CRM_Utils_Array::value('includeTags', $this->_formValues, array()); - $this->_excludeTags = CRM_Utils_Array::value('excludeTags', $this->_formValues, array()); + $this->_includeGroups = CRM_Utils_Array::value('includeGroups', $this->_formValues, []); + $this->_excludeGroups = CRM_Utils_Array::value('excludeGroups', $this->_formValues, []); + $this->_includeTags = CRM_Utils_Array::value('includeTags', $this->_formValues, []); + $this->_excludeTags = CRM_Utils_Array::value('excludeTags', $this->_formValues, []); //define variables $this->_allSearch = FALSE; @@ -95,19 +95,19 @@ public function buildForm(&$form) { $groups = CRM_Core_PseudoConstant::nestedGroup(); - $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); if (count($groups) == 0 || count($tags) == 0) { CRM_Core_Session::setStatus(ts("At least one Group and Tag must be present for Custom Group / Tag search."), ts('Missing Group/Tag')); $url = CRM_Utils_System::url('civicrm/contact/search/custom/list', 'reset=1'); CRM_Utils_System::redirect($url); } - $select2style = array( + $select2style = [ 'multiple' => TRUE, 'style' => 'width: 100%; max-width: 60em;', 'class' => 'crm-select2', 'placeholder' => ts('- select -'), - ); + ]; $form->add('select', 'includeGroups', ts('Include Group(s)'), @@ -123,10 +123,10 @@ public function buildForm(&$form) { $select2style ); - $andOr = array( + $andOr = [ '1' => ts('Show contacts that meet the Groups criteria AND the Tags criteria'), '0' => ts('Show contacts that meet the Groups criteria OR the Tags criteria'), - ); + ]; $form->addRadio('andOr', ts('AND/OR'), $andOr, NULL, '
    ', TRUE); $form->add('select', 'includeTags', @@ -147,17 +147,18 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('includeGroups', 'excludeGroups', 'andOr', 'includeTags', 'excludeTags')); + $form->assign('elements', ['includeGroups', 'excludeGroups', 'andOr', 'includeTags', 'excludeTags']); } /** * @param int $offset * @param int $rowcount - * @param NULL $sort + * @param string $sort * @param bool $includeContactIDs * @param bool $justIDs * * @return string + * @throws \Exception */ public function all( $offset = 0, $rowcount = 0, $sort = NULL, @@ -247,7 +248,7 @@ public function from() { $this->_tableName = "civicrm_temp_custom_{$randomNum}"; //block for Group search - $smartGroup = array(); + $smartGroup = []; if ($this->_groups || $this->_allSearch) { $group = new CRM_Contact_DAO_Group(); $group->is_active = 1; @@ -260,6 +261,12 @@ public function from() { } $includedGroups = implode(',', $allGroups); + //CRM-15049 - Include child group ids. + $childGroupIds = CRM_Contact_BAO_Group::getChildGroupIds($this->_includeGroups); + if (count($childGroupIds) > 0) { + $this->_includeGroups = array_merge($this->_includeGroups, $childGroupIds); + } + if (!empty($this->_includeGroups)) { $iGroups = implode(',', $this->_includeGroups); } @@ -497,8 +504,8 @@ public function from() { /* * Set from statement depending on array sel */ - $whereitems = array(); - foreach (array('Ig', 'It') as $inc) { + $whereitems = []; + foreach (['Ig', 'It'] as $inc) { if ($this->_andOr == 1) { if ($$inc) { $from .= " INNER JOIN {$inc}_{$this->_tableName} temptable$inc ON (contact_a.id = temptable$inc.contact_id)"; @@ -514,7 +521,7 @@ public function from() { } } $this->_where = $whereitems ? "(" . implode(' OR ', $whereitems) . ')' : '(1)'; - foreach (array('Xg', 'Xt') as $exc) { + foreach (['Xg', 'Xt'] as $exc) { if ($$exc) { $from .= " LEFT JOIN {$exc}_{$this->_tableName} temptable$exc ON (contact_a.id = temptable$exc.contact_id)"; $this->_where .= " AND temptable$exc.contact_id IS NULL"; @@ -541,7 +548,7 @@ public function from() { */ public function where($includeContactIDs = FALSE) { if ($includeContactIDs) { - $contactIDs = array(); + $contactIDs = []; foreach ($this->_formValues as $id => $value) { if ($value && @@ -581,10 +588,11 @@ public function count() { /** * @param int $offset * @param int $rowcount - * @param NULL $sort + * @param string $sort * @param bool $returnSQL * * @return string + * @throws \Exception */ public function contactIDs($offset = 0, $rowcount = 0, $sort = NULL, $returnSQL = FALSE) { return $this->all($offset, $rowcount, $sort, FALSE, TRUE); diff --git a/CRM/Contact/Form/Search/Custom/MultipleValues.php b/CRM/Contact/Form/Search/Custom/MultipleValues.php index ad6809484c22..2737366bdb1e 100644 --- a/CRM/Contact/Form/Search/Custom/MultipleValues.php +++ b/CRM/Contact/Form/Search/Custom/MultipleValues.php @@ -1,9 +1,9 @@ _groupTree = CRM_Core_BAO_CustomGroup::getTree("'Contact', 'Individual', 'Organization', 'Household'", - CRM_Core_DAO::$_nullObject, - NULL, -1 - ); + $this->_groupTree = CRM_Core_BAO_CustomGroup::getTree("'Contact', 'Individual', 'Organization', 'Household'", NULL, NULL, -1); $this->_group = CRM_Utils_Array::value('group', $this->_formValues); $this->_tag = CRM_Utils_Array::value('tag', $this->_formValues); - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Contact Type') => 'contact_type', ts('Name') => 'sort_name', - ); + ]; $this->_customGroupIDs = CRM_Utils_Array::value('custom_group', $formValues); @@ -73,7 +70,7 @@ public function __construct(&$formValues) { * Add all the fields for chosen groups */ public function addColumns() { - $this->_tables = array(); + $this->_tables = []; foreach ($this->_groupTree as $groupID => $group) { if (empty($this->_customGroupIDs[$groupID])) { continue; @@ -85,7 +82,7 @@ public function addColumns() { foreach ($group['fields'] as $fieldID => $field) { $this->_columns[$field['label']] = "custom_{$field['id']}"; if (!array_key_exists($group['table_name'], $this->_tables)) { - $this->_tables[$group['table_name']] = array(); + $this->_tables[$group['table_name']] = []; } $this->_tables[$group['table_name']][$field['id']] = $field['column_name']; } @@ -101,16 +98,16 @@ public function buildForm(&$form) { $form->add('text', 'sort_name', ts('Contact Name'), TRUE); - $contactTypes = array('' => ts('- any contact type -')) + CRM_Contact_BAO_ContactType::getSelectElements(); - $form->add('select', 'contact_type', ts('Find...'), $contactTypes, array('class' => 'crm-select2 huge')); + $contactTypes = ['' => ts('- any contact type -')] + CRM_Contact_BAO_ContactType::getSelectElements(); + $form->add('select', 'contact_type', ts('Find...'), $contactTypes, ['class' => 'crm-select2 huge']); // add select for groups - $group = array('' => ts('- any group -')) + CRM_Core_PseudoConstant::group(); - $form->addElement('select', 'group', ts('in'), $group, array('class' => 'crm-select2 huge')); + $group = ['' => ts('- any group -')] + CRM_Core_PseudoConstant::group(); + $form->addElement('select', 'group', ts('in'), $group, ['class' => 'crm-select2 huge']); // add select for tags - $tag = array('' => ts('- any tag -')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); - $form->addElement('select', 'tag', ts('Tagged'), $tag, array('class' => 'crm-select2 huge')); + $tag = ['' => ts('- any tag -')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); + $form->addElement('select', 'tag', ts('Tagged'), $tag, ['class' => 'crm-select2 huge']); if (empty($this->_groupTree)) { CRM_Core_Error::statusBounce(ts("Atleast one Custom Group must be present, for Custom Group search."), @@ -181,7 +178,7 @@ public function all($offset = 0, $rowcount = 0, $sort = NULL, $includeContactIDs "; } - $customClauses = array(); + $customClauses = []; foreach ($this->_tables as $tableName => $fields) { foreach ($fields as $fieldID => $fieldName) { $customClauses[] = "{$tableName}.{$fieldName} as custom_{$fieldID}"; @@ -201,7 +198,7 @@ public function all($offset = 0, $rowcount = 0, $sort = NULL, $includeContactIDs public function from() { $this->buildACLClause('contact_a'); $from = "FROM civicrm_contact contact_a {$this->_aclFrom}"; - $customFrom = array(); + $customFrom = []; // lets do an INNER JOIN so we get only relevant values rather than all values if (!empty($this->_tables)) { foreach ($this->_tables as $tableName => $fields) { @@ -231,8 +228,8 @@ public function from() { */ public function where($includeContactIDs = FALSE) { $count = 1; - $clause = array(); - $params = array(); + $clause = []; + $params = []; $name = CRM_Utils_Array::value('sort_name', $this->_formValues ); @@ -240,7 +237,7 @@ public function where($includeContactIDs = FALSE) { if (strpos($name, '%') === FALSE) { $name = "%{$name}%"; } - $params[$count] = array($name, 'String'); + $params[$count] = [$name, 'String']; $clause[] = "contact_a.sort_name LIKE %{$count}"; $count++; } @@ -249,7 +246,7 @@ public function where($includeContactIDs = FALSE) { $this->_formValues ); if ($contact_type != NULL) { - $contactType = explode('__', $contact_type); + $contactType = explode('__', $contact_type, 2); if (count($contactType) > 1) { $clause[] = "contact_a.contact_type = '$contactType[0]' AND contact_a.contact_sub_type = '$contactType[1]'"; } diff --git a/CRM/Contact/Form/Search/Custom/PostalMailing.php b/CRM/Contact/Form/Search/Custom/PostalMailing.php index 07f6378b801a..fc6998dbe7f7 100644 --- a/CRM/Contact/Form/Search/Custom/PostalMailing.php +++ b/CRM/Contact/Form/Search/Custom/PostalMailing.php @@ -1,9 +1,9 @@ _columns = array( + $this->_columns = [ // If possible, don't use aliases for the columns you select. // You can prefix columns with table aliases, if needed. // @@ -59,21 +60,21 @@ public function __construct(&$formValues) { // If you don't do this, the patch of CRM-16587 might cause database // errors. ts('State') => 'state_province.name', - ); + ]; } /** * @param CRM_Core_Form $form */ public function buildForm(&$form) { - $groups = array('' => ts('- select group -')) + CRM_Core_PseudoConstant::nestedGroup(FALSE); - $form->addElement('select', 'group_id', ts('Group'), $groups, array('class' => 'crm-select2 huge')); + $groups = ['' => ts('- select group -')] + CRM_Core_PseudoConstant::nestedGroup(FALSE); + $form->addElement('select', 'group_id', ts('Group'), $groups, ['class' => 'crm-select2 huge']); /** * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('group_id')); + $form->assign('elements', ['group_id']); } /** @@ -148,15 +149,15 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $params = array(); + $params = []; $count = 1; - $clause = array(); + $clause = []; $groupID = CRM_Utils_Array::value('group_id', $this->_formValues ); if ($groupID) { - $params[$count] = array($groupID, 'Integer'); + $params[$count] = [$groupID, 'Integer']; $clause[] = "cgc.group_id = %{$count}"; } diff --git a/CRM/Contact/Form/Search/Custom/PriceSet.php b/CRM/Contact/Form/Search/Custom/PriceSet.php index 78ad2a2540f7..3253fffb8131 100644 --- a/CRM/Contact/Form/Search/Custom/PriceSet.php +++ b/CRM/Contact/Form/Search/Custom/PriceSet.php @@ -1,9 +1,9 @@ _columns as $dontCare => $fieldName) { - if (in_array($fieldName, array( + if (in_array($fieldName, [ 'contact_id', 'participant_id', 'display_name', - ))) { + ])) { continue; } $sql .= "{$fieldName} int default 0,\n"; @@ -133,12 +133,12 @@ public function fillTable() { $dao = CRM_Core_DAO::executeQuery($sql); // first store all the information by option value id - $rows = array(); + $rows = []; while ($dao->fetch()) { $contactID = $dao->contact_id; $participantID = $dao->participant_id; if (!isset($rows[$participantID])) { - $rows[$participantID] = array(); + $rows[$participantID] = []; } $rows[$participantID][] = "price_field_{$dao->price_field_value_id} = {$dao->qty}"; @@ -146,12 +146,14 @@ public function fillTable() { foreach (array_keys($rows) as $participantID) { $values = implode(',', $rows[$participantID]); - $sql = " + if ($values) { + $sql = " UPDATE {$this->_tableName} SET $values WHERE participant_id = $participantID; "; - CRM_Core_DAO::executeQuery($sql); + CRM_Core_DAO::executeQuery($sql); + } } } @@ -174,9 +176,9 @@ public function priceSetDAO($eventID = NULL) { AND p.entity_id = e.id "; - $params = array(); + $params = []; if ($eventID) { - $params[1] = array($eventID, 'Integer'); + $params[1] = [$eventID, 'Integer']; $sql .= " AND e.id = $eventID"; } @@ -194,7 +196,7 @@ public function priceSetDAO($eventID = NULL) { public function buildForm(&$form) { $dao = $this->priceSetDAO(); - $event = array(); + $event = []; while ($dao->fetch()) { $event[$dao->id] = $dao->title; } @@ -219,15 +221,15 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('event_id')); + $form->assign('elements', ['event_id']); } public function setColumns() { - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Participant ID') => 'participant_id', ts('Name') => 'display_name', - ); + ]; if (!$this->_eventID) { return; @@ -290,10 +292,10 @@ public function all( contact_a.display_name as display_name"; foreach ($this->_columns as $dontCare => $fieldName) { - if (in_array($fieldName, array( + if (in_array($fieldName, [ 'contact_id', 'display_name', - ))) { + ])) { continue; } $selectClause .= ",\ntempTable.{$fieldName} as {$fieldName}"; diff --git a/CRM/Contact/Form/Search/Custom/Proximity.php b/CRM/Contact/Form/Search/Custom/Proximity.php index 8375adee013f..dc517d92c8ec 100644 --- a/CRM/Contact/Form/Search/Custom/Proximity.php +++ b/CRM/Contact/Form/Search/Custom/Proximity.php @@ -1,9 +1,9 @@ _formValues)) { // add the country and state - if (!empty($this->_formValues['country_id'])) { - $this->_formValues['country'] = CRM_Core_PseudoConstant::country($this->_formValues['country_id']); - } - - if (!empty($this->_formValues['state_province_id'])) { - $this->_formValues['state_province'] = CRM_Core_PseudoConstant::stateProvince($this->_formValues['state_province_id']); - } - - // use the address to get the latitude and longitude - CRM_Utils_Geocode_Google::format($this->_formValues); - - if (!is_numeric(CRM_Utils_Array::value('geo_code_1', $this->_formValues)) || - !is_numeric(CRM_Utils_Array::value('geo_code_2', $this->_formValues)) || - !isset($this->_formValues['distance']) - ) { - CRM_Core_Error::fatal(ts('Could not geocode input')); - } - + self::addGeocodingData($this->_formValues); $this->_latitude = $this->_formValues['geo_code_1']; $this->_longitude = $this->_formValues['geo_code_2']; @@ -88,14 +74,23 @@ public function __construct(&$formValues) { $this->_tag = CRM_Utils_Array::value('tag', $this->_formValues); - $this->_columns = array( + $this->_columns = [ ts('Name') => 'sort_name', ts('Street Address') => 'street_address', ts('City') => 'city', ts('Postal Code') => 'postal_code', ts('State') => 'state_province', ts('Country') => 'country', - ); + ]; + } + + /** + * Get the query object for this selector. + * + * @return CRM_Contact_BAO_Query + */ + public function getQueryObj() { + return $this->_query; } /** @@ -108,7 +103,7 @@ public function buildForm(&$form) { $form->add('text', 'distance', ts('Distance'), NULL, TRUE); - $proxUnits = array('km' => ts('km'), 'miles' => ts('miles')); + $proxUnits = ['km' => ts('km'), 'miles' => ts('miles')]; $form->add('select', 'prox_distance_unit', ts('Units'), $proxUnits, TRUE); $form->add('text', @@ -126,20 +121,23 @@ public function buildForm(&$form) { ts('Postal Code') ); - $defaults = array(); + $defaults = []; if ($countryDefault) { $defaults['country_id'] = $countryDefault; } $form->addChainSelect('state_province_id'); - $country = array('' => ts('- select -')) + CRM_Core_PseudoConstant::country(); - $form->add('select', 'country_id', ts('Country'), $country, TRUE, array('class' => 'crm-select2')); + $country = ['' => ts('- select -')] + CRM_Core_PseudoConstant::country(); + $form->add('select', 'country_id', ts('Country'), $country, TRUE, ['class' => 'crm-select2']); + + $form->add('text', 'geo_code_1', ts('Latitude')); + $form->add('text', 'geo_code_2', ts('Longitude')); - $group = array('' => ts('- any group -')) + CRM_Core_PseudoConstant::nestedGroup(); - $form->addElement('select', 'group', ts('Group'), $group, array('class' => 'crm-select2 huge')); + $group = ['' => ts('- any group -')] + CRM_Core_PseudoConstant::nestedGroup(); + $form->addElement('select', 'group', ts('Group'), $group, ['class' => 'crm-select2 huge']); - $tag = array('' => ts('- any tag -')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); - $form->addElement('select', 'tag', ts('Tag'), $tag, array('class' => 'crm-select2 huge')); + $tag = ['' => ts('- any tag -')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); + $form->addElement('select', 'tag', ts('Tag'), $tag, ['class' => 'crm-select2 huge']); /** * You can define a custom title for the search form @@ -150,7 +148,7 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array( + $form->assign('elements', [ 'distance', 'prox_distance_unit', 'street_address', @@ -160,7 +158,7 @@ public function buildForm(&$form) { 'state_province_id', 'group', 'tag', - )); + ]); } /** @@ -176,20 +174,7 @@ public function all( $offset = 0, $rowcount = 0, $sort = NULL, $includeContactIDs = FALSE, $justIDs = FALSE ) { - if ($justIDs) { - $selectClause = "contact_a.id as contact_id"; - } - else { - $selectClause = " -contact_a.id as contact_id , -contact_a.sort_name as sort_name , -address.street_address as street_address, -address.city as city , -address.postal_code as postal_code , -state_province.name as state_province, -country.name as country -"; - } + $selectClause = $justIDs ? "contact_a.id as contact_id" : NULL; return $this->sql($selectClause, $offset, $rowcount, $sort, @@ -198,31 +183,70 @@ public function all( } /** + * Override sql() function to use the Query object rather than generating on the form. + * + * @param string $selectClause + * @param int $offset + * @param int $rowcount + * @param null $sort + * @param bool $includeContactIDs + * @param null $groupBy + * * @return string */ - public function from() { - $this->buildACLClause('contact_a'); - $f = " -FROM civicrm_contact contact_a -LEFT JOIN civicrm_address address ON ( address.contact_id = contact_a.id AND - address.is_primary = 1 ) -LEFT JOIN civicrm_state_province state_province ON state_province.id = address.state_province_id -LEFT JOIN civicrm_country country ON country.id = address.country_id {$this->_aclFrom} -"; - - // This prevents duplicate rows when contacts have more than one tag any you select "any tag" - if ($this->_tag) { - $f .= " -LEFT JOIN civicrm_entity_tag t ON (t.entity_table='civicrm_contact' AND contact_a.id = t.entity_id) -"; + public function sql( + $selectClause, + $offset = 0, + $rowcount = 0, + $sort = NULL, + $includeContactIDs = FALSE, + $groupBy = NULL + ) { + + $isCountOnly = FALSE; + if ($selectClause === 'count(distinct contact_a.id) as total') { + $isCountOnly = TRUE; } - if ($this->_group) { - $f .= " -LEFT JOIN civicrm_group_contact cgc ON ( cgc.contact_id = contact_a.id AND cgc.status = 'Added') -"; + + $searchParams = [ + ['prox_distance_unit', '=', $this->_formValues['prox_distance_unit'], 0, 0], + ['prox_distance', '=', $this->_formValues['distance'], 0, 0], + ['prox_geo_code_1', '=', $this->_formValues['geo_code_1'], 0, 0], + ['prox_geo_code_2', '=', $this->_formValues['geo_code_2'], 0, 0], + ]; + if (!empty($this->_formValues['group'])) { + $searchParams[] = ['group', '=', ['IN', (array) $this->_formValues['group']][1], 0, 0]; + } + if (!empty($this->_formValues['tag'])) { + $searchParams[] = ['contact_tags', '=', ['IN', (array) $this->_formValues['tag']][1], 0, 0]; } - return $f; + $display = array_fill_keys(['city', 'state_province', 'country', 'postal_code', 'street_address', 'display_name', 'sort_name'], 1); + if ($selectClause === 'contact_a.id as contact_id') { + // Not sure when this would happen but calling all with 'justIDs' gets us here. + $display = ['contact_id' => 1]; + } + + $this->_query = new CRM_Contact_BAO_Query($searchParams, $display); + return $this->_query->searchQuery( + $offset, + $rowcount, + $sort, + $isCountOnly, + $includeContactIDs, + FALSE, + $isCountOnly, + $returnQuery = TRUE + ); + + } + + /** + * @return string + */ + public function from() { + //unused + return ''; } /** @@ -231,33 +255,8 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $params = array(); - $clause = array(); - - $where = CRM_Contact_BAO_ProximityQuery::where($this->_latitude, - $this->_longitude, - $this->_distance, - 'address' - ); - - if ($this->_tag) { - $where .= " -AND t.tag_id = {$this->_tag} -"; - } - if ($this->_group) { - $where .= " -AND cgc.group_id = {$this->_group} - "; - } - - $where .= " AND contact_a.is_deleted != 1 "; - - if ($this->_aclWhere) { - $where .= " AND {$this->_aclWhere} "; - } - - return $this->whereClause($where, $params); + //unused + return ''; } /** @@ -277,7 +276,7 @@ public function setDefaultValues() { $config = CRM_Core_Config::singleton(); $countryDefault = $config->defaultContactCountry; $stateprovinceDefault = $config->defaultContactStateProvince; - $defaults = array(); + $defaults = []; if ($countryDefault) { if ($countryDefault == '1228' || $countryDefault == '1226') { @@ -314,10 +313,42 @@ public function setTitle($title) { } /** - * @param string $tableAlias + * Validate form input. + * + * @param array $fields + * @param array $files + * @param CRM_Core_Form $self + * + * @return array + * Input errors from the form. + */ + public function formRule($fields, $files, $self) { + $this->addGeocodingData($fields); + + if (!is_numeric(CRM_Utils_Array::value('geo_code_1', $fields)) || + !is_numeric(CRM_Utils_Array::value('geo_code_2', $fields)) || + !isset($fields['distance']) + ) { + $errorMessage = ts('Could not determine co-ordinates for provided data'); + return array_fill_keys(['street_address', 'city', 'postal_code', 'country_id', 'state_province_id'], $errorMessage); + } + return []; + } + + /** + * Add the geocoding data to the fields supplied. + * + * @param array $fields */ - public function buildACLClause($tableAlias = 'contact') { - list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); + protected function addGeocodingData(&$fields) { + if (!empty($fields['country_id'])) { + $fields['country'] = CRM_Core_PseudoConstant::country($fields['country_id']); + } + + if (!empty($fields['state_province_id'])) { + $fields['state_province'] = CRM_Core_PseudoConstant::stateProvince($fields['state_province_id']); + } + CRM_Core_BAO_Address::addGeocoderData($fields); } } diff --git a/CRM/Contact/Form/Search/Custom/RandomSegment.php b/CRM/Contact/Form/Search/Custom/RandomSegment.php index a0125ed0538b..36addc77c6c9 100644 --- a/CRM/Contact/Form/Search/Custom/RandomSegment.php +++ b/CRM/Contact/Form/Search/Custom/RandomSegment.php @@ -1,9 +1,9 @@ _columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Contact Type') => 'contact_type', ts('Name') => 'sort_name', ts('Email') => 'email', - ); + ]; $this->initialize(); } @@ -87,12 +87,12 @@ public function buildForm(&$form) { $groups = CRM_Core_PseudoConstant::nestedGroup(); - $select2style = array( + $select2style = [ 'multiple' => TRUE, 'style' => 'width: 100%; max-width: 60em;', 'class' => 'crm-select2', 'placeholder' => ts('- select -'), - ); + ]; $form->add('select', 'includeGroups', ts('Include Group(s)'), @@ -114,7 +114,7 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('segmentSize', 'includeGroups', 'excludeGroups')); + $form->assign('elements', ['segmentSize', 'includeGroups', 'excludeGroups']); } /** @@ -162,7 +162,7 @@ public function from() { $this->_tableName = "civicrm_temp_custom_{$randomNum}"; //block for Group search - $smartGroup = array(); + $smartGroup = []; $group = new CRM_Contact_DAO_Group(); $group->is_active = 1; $group->find(); @@ -372,6 +372,7 @@ public function setTitle($title) { /** * @return mixed */ + /** * @return mixed */ diff --git a/CRM/Contact/Form/Search/Custom/Sample.php b/CRM/Contact/Form/Search/Custom/Sample.php index c5e2067c5878..a148f5ca3d48 100644 --- a/CRM/Contact/Form/Search/Custom/Sample.php +++ b/CRM/Contact/Form/Search/Custom/Sample.php @@ -1,9 +1,9 @@ _columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Contact Type') => 'contact_type', ts('Name') => 'sort_name', ts('State') => 'state_province', - ); + ]; } /** @@ -69,7 +70,7 @@ public function buildForm(&$form) { TRUE ); - $stateProvince = array('' => ts('- any state/province -')) + CRM_Core_PseudoConstant::stateProvince(); + $stateProvince = ['' => ts('- any state/province -')] + CRM_Core_PseudoConstant::stateProvince(); $form->addElement('select', 'state_province_id', ts('State/Province'), $stateProvince); /** @@ -81,17 +82,17 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('household_name', 'state_province_id')); + $form->assign('elements', ['household_name', 'state_province_id']); } /** * @return array */ public function summary() { - $summary = array( + $summary = [ 'summary' => 'This is a summary', 'total' => 50.0, - ); + ]; return $summary; } @@ -158,11 +159,11 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $params = array(); + $params = []; $where = "contact_a.contact_type = 'Household'"; $count = 1; - $clause = array(); + $clause = []; $name = CRM_Utils_Array::value('household_name', $this->_formValues ); @@ -170,7 +171,7 @@ public function where($includeContactIDs = FALSE) { if (strpos($name, '%') === FALSE) { $name = "%{$name}%"; } - $params[$count] = array($name, 'String'); + $params[$count] = [$name, 'String']; $clause[] = "contact_a.household_name LIKE %{$count}"; $count++; } @@ -185,7 +186,7 @@ public function where($includeContactIDs = FALSE) { } if ($state) { - $params[$count] = array($state, 'Integer'); + $params[$count] = [$state, 'Integer']; $clause[] = "state_province.id = %{$count}"; } @@ -211,7 +212,7 @@ public function templateFile() { * @return array */ public function setDefaultValues() { - return array_merge(array('household_name' => ''), $this->_formValues); + return array_merge(['household_name' => ''], $this->_formValues); } /** diff --git a/CRM/Contact/Form/Search/Custom/TagContributions.php b/CRM/Contact/Form/Search/Custom/TagContributions.php index 3987ce399b59..d6bd1b17d8ec 100644 --- a/CRM/Contact/Form/Search/Custom/TagContributions.php +++ b/CRM/Contact/Form/Search/Custom/TagContributions.php @@ -1,9 +1,9 @@ _formValues = $formValues; + $this->_formValues = self::formatSavedSearchFields($formValues); $this->_permissionedComponent = 'CiviContribute'; /** * Define the columns for search result rows */ - $this->_columns = array( + $this->_columns = [ ts('Contact ID') => 'contact_id', ts('Full Name') => 'sort_name', ts('First Name') => 'first_name', ts('Last Name') => 'last_name', ts('Tag') => 'tag_name', ts('Totals') => 'amount', - ); + ]; } /** @@ -73,16 +73,16 @@ public function buildForm(&$form) { * Define the search form fields here */ - $form->addDate('start_date', ts('Contribution Date From'), FALSE, array('formatType' => 'custom')); - $form->addDate('end_date', ts('...through'), FALSE, array('formatType' => 'custom')); - $tag = array('' => ts('- any tag -')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $form->add('datepicker', 'start_date', ts('Contribution Date From'), [], FALSE, ['time' => FALSE]); + $form->add('datepicker', 'end_date', ts('...through'), [], FALSE, ['time' => FALSE]); + $tag = ['' => ts('- any tag -')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); $form->addElement('select', 'tag', ts('Tagged'), $tag); /** * If you are using the sample template, this array tells the template fields to render * for the search form. */ - $form->assign('elements', array('start_date', 'end_date', 'tag')); + $form->assign('elements', ['start_date', 'end_date', 'tag']); } /** @@ -169,25 +169,24 @@ public function from() { * WHERE clause is an array built from any required JOINS plus conditional filters based on search criteria field values * */ + /** * @param bool $includeContactIDs * * @return string */ public function where($includeContactIDs = FALSE) { - $clauses = array(); + $clauses = []; $clauses[] = "contact_a.contact_type = 'Individual'"; $clauses[] = "civicrm_contribution.contact_id = contact_a.id"; - $startDate = CRM_Utils_Date::processDate($this->_formValues['start_date']); - if ($startDate) { - $clauses[] = "civicrm_contribution.receive_date >= $startDate"; + if ($this->_formValues['start_date']) { + $clauses[] = "civicrm_contribution.receive_date >= '{$this->_formValues['start_date']} 00:00:00'"; } - $endDate = CRM_Utils_Date::processDate($this->_formValues['end_date']); - if ($endDate) { - $clauses[] = "civicrm_contribution.receive_date <= $endDate"; + if ($this->_formValues['end_date']) { + $clauses[] = "civicrm_contribution.receive_date <= '{$this->_formValues['end_date']} 23:59:59'"; } $tag = CRM_Utils_Array::value('tag', $this->_formValues); @@ -200,7 +199,7 @@ public function where($includeContactIDs = FALSE) { } if ($includeContactIDs) { - $contactIDs = array(); + $contactIDs = []; foreach ($this->_formValues as $id => $value) { if ($value && substr($id, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX @@ -220,7 +219,6 @@ public function where($includeContactIDs = FALSE) { return implode(' AND ', $clauses); } - /* * Functions below generally don't need to be modified */ @@ -231,9 +229,7 @@ public function where($includeContactIDs = FALSE) { public function count() { $sql = $this->all(); - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); return $dao->N; } @@ -282,4 +278,27 @@ public function buildACLClause($tableAlias = 'contact') { list($this->_aclFrom, $this->_aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause($tableAlias); } + /** + * Format saved search fields for this custom group. + * + * Note this is a function to facilitate the transition to jcalendar for + * saved search groups. In time it can be stripped out again. + * + * @param array $formValues + * + * @return array + */ + public static function formatSavedSearchFields($formValues) { + $dateFields = [ + 'start_date', + 'end_date', + ]; + foreach ($formValues as $element => $value) { + if (in_array($element, $dateFields) && !empty($value)) { + $formValues[$element] = date('Y-m-d', strtotime($value)); + } + } + return $formValues; + } + } diff --git a/CRM/Contact/Form/Search/Custom/ZipCodeRange.php b/CRM/Contact/Form/Search/Custom/ZipCodeRange.php index 349d57d81898..3b341bcb2818 100644 --- a/CRM/Contact/Form/Search/Custom/ZipCodeRange.php +++ b/CRM/Contact/Form/Search/Custom/ZipCodeRange.php @@ -1,9 +1,9 @@ _columns = array( + $this->_columns = [ // If possible, don't use aliases for the columns you select. // You can prefix columns with table aliases, if needed. // @@ -53,7 +54,7 @@ public function __construct(&$formValues) { ts('Name') => 'sort_name', ts('Email') => 'email', ts('Zip') => 'postal_code', - ); + ]; } /** @@ -81,14 +82,14 @@ public function buildForm(&$form) { * if you are using the standard template, this array tells the template what elements * are part of the search criteria */ - $form->assign('elements', array('postal_code_low', 'postal_code_high')); + $form->assign('elements', ['postal_code_low', 'postal_code_high']); } /** * @return array */ public function summary() { - $summary = array(); + $summary = []; return $summary; } @@ -161,7 +162,7 @@ public function from() { * @return string */ public function where($includeContactIDs = FALSE) { - $params = array(); + $params = []; $low = CRM_Utils_Array::value('postal_code_low', $this->_formValues @@ -179,10 +180,10 @@ public function where($includeContactIDs = FALSE) { } $where = "ROUND(address.postal_code) >= %1 AND ROUND(address.postal_code) <= %2"; - $params = array( - 1 => array(trim($low), 'Integer'), - 2 => array(trim($high), 'Integer'), - ); + $params = [ + 1 => [trim($low), 'Integer'], + 2 => [trim($high), 'Integer'], + ]; if ($this->_aclWhere) { $where .= " AND {$this->_aclWhere} "; diff --git a/CRM/Contact/Form/Search/Interface.php b/CRM/Contact/Form/Search/Interface.php index 071962c0bc1c..1728708bc7bd 100644 --- a/CRM/Contact/Form/Search/Interface.php +++ b/CRM/Contact/Form/Search/Interface.php @@ -1,9 +1,9 @@ _contactIds = []; + $form->_contactTypes = []; - $form->_contactIds = array(); - $form->_contactTypes = array(); + $useTable = (CRM_Utils_System::getClassName($form->controller->getStateMachine()) == 'CRM_Export_StateMachine_Standalone'); + + $isStandAlone = in_array('task', $form->urlPath) || in_array('standalone', $form->urlPath); + if ($isStandAlone) { + list($form->_task, $title) = CRM_Contact_Task::getTaskAndTitleByClass(get_class($form)); + if (!array_key_exists($form->_task, CRM_Contact_Task::permissionedTaskTitles(CRM_Core_Permission::getPermission()))) { + CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.')); + } + $form->_contactIds = explode(',', CRM_Utils_Request::retrieve('cids', 'CommaSeparatedIntegers', $form, TRUE)); + if (empty($form->_contactIds)) { + CRM_Core_Error::statusBounce(ts('No Contacts Selected')); + } + $form->setTitle($title); + } // get the submitted values of the search form // we'll need to get fv from either search or adv search in the future @@ -116,7 +132,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { self::$_searchFormValues = $form->controller->exportValues('Custom'); $fragment .= '/custom'; } - else { + elseif (!$isStandAlone) { self::$_searchFormValues = $form->controller->exportValues('Basic'); } @@ -138,35 +154,30 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $form->assign('taskName', CRM_Utils_Array::value($form->_task, $crmContactTaskTasks)); if ($useTable) { - $form->_componentTable = CRM_Core_DAO::createTempTableName('civicrm_task_action', TRUE, $qfKey); - $sql = " DROP TABLE IF EXISTS {$form->_componentTable}"; - CRM_Core_DAO::executeQuery($sql); - - $sql = "CREATE TABLE {$form->_componentTable} ( contact_id int primary key) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci"; - CRM_Core_DAO::executeQuery($sql); + $tempTable = CRM_Utils_SQL_TempTable::build()->setCategory('tskact')->setDurable()->setId($qfKey); + $form->_componentTable = $tempTable->getName(); + $tempTable->drop(); + $tempTable->createWithColumns('contact_id int primary key'); } // all contacts or action = save a search if ((CRM_Utils_Array::value('radio_ts', self::$_searchFormValues) == 'ts_all') || ($form->_task == CRM_Contact_Task::SAVE_SEARCH) ) { - $sortByCharacter = $form->get('sortByCharacter'); - $cacheKey = ($sortByCharacter && $sortByCharacter != 'all') ? "{$cacheKey}_alphabet" : $cacheKey; - // since we don't store all contacts in prevnextcache, when user selects "all" use query to retrieve contacts // rather than prevnext cache table for most of the task actions except export where we rebuild query to fetch // final result set if ($useTable) { - $allCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey, "getall"); + $allCids = Civi::service('prevnext')->getSelection($cacheKey, "getall"); } else { - $allCids[$cacheKey] = $form->getContactIds(); + $allCids[$cacheKey] = self::getContactIds($form); } - $form->_contactIds = array(); + $form->_contactIds = []; if ($useTable) { $count = 0; - $insertString = array(); + $insertString = []; foreach ($allCids[$cacheKey] as $cid => $ignore) { $count++; $insertString[] = " ( {$cid} ) "; @@ -174,7 +185,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $string = implode(',', $insertString); $sql = "REPLACE INTO {$form->_componentTable} ( contact_id ) VALUES $string"; CRM_Core_DAO::executeQuery($sql); - $insertString = array(); + $insertString = []; } } if (!empty($insertString)) { @@ -183,7 +194,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { CRM_Core_DAO::executeQuery($sql); } } - else { + elseif (empty($form->_contactIds)) { // filter duplicates here // CRM-7058 // might be better to do this in the query, but that logic is a bit complex @@ -198,7 +209,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { elseif (CRM_Utils_Array::value('radio_ts', self::$_searchFormValues) == 'ts_sel') { // selected contacts only // need to perform action on only selected contacts - $insertString = array(); + $insertString = []; // refire sql in case of custom search if ($form->_action == CRM_Core_Action::COPY) { @@ -218,7 +229,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { } else { // fetching selected contact ids of passed cache key - $selectedCids = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey); + $selectedCids = Civi::service('prevnext')->getSelection($cacheKey); foreach ($selectedCids[$cacheKey] as $selectedCid => $ignore) { if ($useTable) { $insertString[] = " ( {$selectedCid} ) "; @@ -258,7 +269,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { ) { $sel = CRM_Utils_Array::value('radio_ts', self::$_searchFormValues); $form->assign('searchtype', $sel); - $result = CRM_Core_BAO_PrevNextCache::getSelectedContacts(); + $result = self::getSelectedContactNames(); $form->assign("value", $result); } @@ -271,47 +282,50 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { } /** - * Get the contact id for custom search. + * Get the contact ids for: + * - "Select Records: All xx records" + * - custom search (FIXME: does this still apply to custom search?). + * When we call this function we are not using the prev/next cache + * + * @param $form CRM_Core_Form * - * we are not using prev/next table in case of custom search + * @return array $contactIds */ - public function getContactIds() { + public static function getContactIds($form) { // need to perform action on all contacts // fire the query again and get the contact id's + display name $sortID = NULL; - if ($this->get(CRM_Utils_Sort::SORT_ID)) { - $sortID = CRM_Utils_Sort::sortIDValue($this->get(CRM_Utils_Sort::SORT_ID), - $this->get(CRM_Utils_Sort::SORT_DIRECTION) + if ($form->get(CRM_Utils_Sort::SORT_ID)) { + $sortID = CRM_Utils_Sort::sortIDValue($form->get(CRM_Utils_Sort::SORT_ID), + $form->get(CRM_Utils_Sort::SORT_DIRECTION) ); } - $selectorName = $this->controller->selectorName(); - require_once str_replace('_', DIRECTORY_SEPARATOR, $selectorName) . '.php'; + $selectorName = $form->controller->selectorName(); - $fv = $this->get('formValues'); - $customClass = $this->get('customSearchClass'); - require_once 'CRM/Core/BAO/Mapping.php'; + $fv = $form->get('formValues'); + $customClass = $form->get('customSearchClass'); $returnProperties = CRM_Core_BAO_Mapping::returnProperties(self::$_searchFormValues); $selector = new $selectorName($customClass, $fv, NULL, $returnProperties); - $params = $this->get('queryParams'); + $params = $form->get('queryParams'); // fix for CRM-5165 - $sortByCharacter = $this->get('sortByCharacter'); + $sortByCharacter = $form->get('sortByCharacter'); if ($sortByCharacter && $sortByCharacter != 1) { - $params[] = array('sortByCharacter', '=', $sortByCharacter, 0, 0); + $params[] = ['sortByCharacter', '=', $sortByCharacter, 0, 0]; } - $queryOperator = $this->get('queryOperator'); + $queryOperator = $form->get('queryOperator'); if (!$queryOperator) { $queryOperator = 'AND'; } - $dao = $selector->contactIDQuery($params, $this->_action, $sortID, + $dao = $selector->contactIDQuery($params, $sortID, CRM_Utils_Array::value('display_relationship_type', $fv), $queryOperator ); - $contactIds = array(); + $contactIds = []; while ($dao->fetch()) { $contactIds[$dao->contact_id] = $dao->contact_id; } @@ -319,7 +333,6 @@ public function getContactIds() { return $contactIds; } - /** * Set default values for the form. Relationship that in edit/view action. * @@ -328,7 +341,7 @@ public function getContactIds() { * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; return $defaults; } @@ -364,19 +377,18 @@ public function postProcess() { * @param bool $submitOnce */ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) { - $this->addButtons(array( - array( - 'type' => $nextType, - 'name' => $title, - 'isDefault' => TRUE, - ), - array( - 'type' => $backType, - 'name' => ts('Cancel'), - 'icon' => 'fa-times', - ), - ) - ); + $this->addButtons([ + [ + 'type' => $nextType, + 'name' => $title, + 'isDefault' => TRUE, + ], + [ + 'type' => $backType, + 'name' => ts('Cancel'), + 'icon' => 'fa-times', + ], + ]); } /** @@ -402,10 +414,10 @@ public function mergeContactIdsByHousehold() { // Get Head of Household & Household Member relationships $relationKeyMOH = CRM_Utils_Array::key('Household Member of', $contactRelationshipTypes); $relationKeyHOH = CRM_Utils_Array::key('Head of Household for', $contactRelationshipTypes); - $householdRelationshipTypes = array( + $householdRelationshipTypes = [ $relationKeyMOH => $contactRelationshipTypes[$relationKeyMOH], $relationKeyHOH => $contactRelationshipTypes[$relationKeyHOH], - ); + ]; $relID = implode(',', $this->_contactIds); @@ -441,25 +453,46 @@ public function mergeContactIdsByHousehold() { $this->_contactIds[] = $householdsDAO->household_id; } } - $householdsDAO->free(); } // If contact list has changed, households will probably be at the end of // the list. Sort it again by sort_name. if (implode(',', $this->_contactIds) != $relID) { - $contact_sort = array(); - $result = civicrm_api3('Contact', 'get', array( - 'return' => array('id'), - 'id' => array('IN' => $this->_contactIds), - 'options' => array( + $result = civicrm_api3('Contact', 'get', [ + 'return' => ['id'], + 'id' => ['IN' => $this->_contactIds], + 'options' => [ 'limit' => 0, 'sort' => "sort_name", - ), - )); + ], + ]); $this->_contactIds = array_keys($result['values']); } } + /** + * @return array + * List of contact names. + * NOTE: These are raw values from the DB. In current data-model, that means + * they are pre-encoded HTML. + */ + private static function getSelectedContactNames() { + $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String'); + $cacheKey = "civicrm search {$qfKey}"; + + $cids = []; + // Gymanstic time! + foreach (Civi::service('prevnext')->getSelection($cacheKey) as $cacheKey => $values) { + $cids = array_unique(array_merge($cids, array_keys($values))); + } + + $result = CRM_Utils_SQL_Select::from('civicrm_contact') + ->where('id IN (#cids)', ['cids' => $cids]) + ->execute() + ->fetchMap('id', 'sort_name'); + return $result; + } + /** * Given this task's list of targets, produce a hidden group. * @@ -472,18 +505,18 @@ public function createHiddenGroup() { $searchParams = $this->controller->exportValues(); if ($searchParams['radio_ts'] == 'ts_sel') { // Create a static group. - - $randID = md5(time() . rand(1, 1000)); // groups require a unique name + // groups require a unique name + $randID = md5(time() . rand(1, 1000)); $grpTitle = "Hidden Group {$randID}"; $grpID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $grpTitle, 'id', 'title'); if (!$grpID) { - $groupParams = array( + $groupParams = [ 'title' => $grpTitle, 'is_active' => 1, 'is_hidden' => 1, - 'group_type' => array('2' => 1), - ); + 'group_type' => ['2' => 1], + ]; $group = CRM_Contact_BAO_Group::create($groupParams); $grpID = $group->id; @@ -491,32 +524,36 @@ public function createHiddenGroup() { CRM_Contact_BAO_GroupContact::addContactsToGroup($this->_contactIds, $group->id); $newGroupTitle = "Hidden Group {$grpID}"; - $groupParams = array( + $groupParams = [ 'id' => $grpID, 'name' => CRM_Utils_String::titleToVar($newGroupTitle), 'title' => $newGroupTitle, - 'group_type' => array('2' => 1), - ); - $group = CRM_Contact_BAO_Group::create($groupParams); + 'group_type' => ['2' => 1], + ]; + CRM_Contact_BAO_Group::create($groupParams); } // note at this point its a static group - return array($grpID, NULL); + return [$grpID, NULL]; } else { // Create a smart group. - $ssId = $this->get('ssID'); - $hiddenSmartParams = array( - 'group_type' => array('2' => 1), - 'form_values' => $this->get('formValues'), + $hiddenSmartParams = [ + 'group_type' => ['2' => 1], + // queryParams have been preprocessed esp WRT any entity reference fields - see + + // https://github.com/civicrm/civicrm-core/pull/13250 + // Advanced search sets queryParams, for builder you need formValues. + // This is kinda fragile but .... see CRM_Mailing_Form_Task_AdhocMailingTest for test effort. + // Moral never touch anything ever again and the house of cards will stand tall, unless there is a breeze + 'form_values' => $this->get('isSearchBuilder') ? $this->get('formValues') : $this->get('queryParams'), 'saved_search_id' => $ssId, 'search_custom_id' => $this->get('customSearchID'), 'search_context' => $this->get('context'), - ); + ]; list($smartGroupId, $savedSearchId) = CRM_Contact_BAO_Group::createHiddenSmartGroup($hiddenSmartParams); - return array($smartGroupId, $savedSearchId); + return [$smartGroupId, $savedSearchId]; } } diff --git a/CRM/Contact/Form/Task/AddToGroup.php b/CRM/Contact/Form/Task/AddToGroup.php index c77d358534c0..a25f920de3ab 100644 --- a/CRM/Contact/Form/Task/AddToGroup.php +++ b/CRM/Contact/Form/Task/AddToGroup.php @@ -1,9 +1,9 @@ _id) { - $this->addRadio('group_option', ts('Group Options'), $options, array('onclick' => "return showElements();")); + $this->addRadio('group_option', ts('Group Options'), $options, ['onclick' => "return showElements();"]); $this->add('text', 'title', ts('Group Name:') . ' ', CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Group', 'title') ); $this->addRule('title', ts('Name already exists in Database.'), - 'objectExists', array('CRM_Contact_DAO_Group', $this->_id, 'title') + 'objectExists', ['CRM_Contact_DAO_Group', $this->_id, 'title'] ); $this->add('textarea', 'description', ts('Description:') . ' ', @@ -115,9 +115,9 @@ public function buildQuickForm() { } // add select for groups - $group = array('' => ts('- select group -')) + CRM_Core_PseudoConstant::nestedGroup(); + $group = ['' => ts('- select group -')] + CRM_Core_PseudoConstant::nestedGroup(); - $groupElement = $this->add('select', 'group_id', ts('Select Group'), $group, FALSE, array('class' => 'crm-select2 huge')); + $groupElement = $this->add('select', 'group_id', ts('Select Group'), $group, FALSE, ['class' => 'crm-select2 huge']); $this->_title = $group[$this->_id]; @@ -125,13 +125,13 @@ public function buildQuickForm() { $groupElement->freeze(); // also set the group title - $groupValues = array('id' => $this->_id, 'title' => $this->_title); + $groupValues = ['id' => $this->_id, 'title' => $this->_title]; $this->assign_by_ref('group', $groupValues); } // Set dynamic page title for 'Add Members Group (confirm)' if ($this->_id) { - CRM_Utils_System::setTitle(ts('Add Contacts: %1', array(1 => $this->_title))); + CRM_Utils_System::setTitle(ts('Add Contacts: %1', [1 => $this->_title])); } else { CRM_Utils_System::setTitle(ts('Add Contacts to A Group')); @@ -148,7 +148,7 @@ public function buildQuickForm() { * the default array reference */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if ($this->_context === 'amtg') { $defaults['group_id'] = $this->_id; @@ -162,7 +162,7 @@ public function setDefaultValues() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_task_AddToGroup', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_AddToGroup', 'formRule']); } /** @@ -174,7 +174,7 @@ public function addRules() { * list of errors to be posted back to the form */ public static function formRule($params) { - $errors = array(); + $errors = []; if (!empty($params['group_option']) && empty($params['title'])) { $errors['title'] = "Group Name is a required field"; @@ -193,7 +193,7 @@ public function postProcess() { $params = $this->controller->exportValues(); $groupOption = CRM_Utils_Array::value('group_option', $params, NULL); if ($groupOption) { - $groupParams = array(); + $groupParams = []; $groupParams['title'] = $params['title']; $groupParams['description'] = $params['description']; $groupParams['visibility'] = "User and User Admin Only"; @@ -219,24 +219,24 @@ public function postProcess() { list($total, $added, $notAdded) = CRM_Contact_BAO_GroupContact::addContactsToGroup($this->_contactIds, $groupID); - $status = array( - ts('%count contact added to group', array( - 'count' => $added, - 'plural' => '%count contacts added to group', - )), - ); + $status = [ + ts('%count contact added to group', [ + 'count' => $added, + 'plural' => '%count contacts added to group', + ]), + ]; if ($notAdded) { - $status[] = ts('%count contact was already in group', array( - 'count' => $notAdded, - 'plural' => '%count contacts were already in group', - )); + $status[] = ts('%count contact was already in group', [ + 'count' => $notAdded, + 'plural' => '%count contacts were already in group', + ]); } $status = '
    • ' . implode('
    • ', $status) . '
    '; - CRM_Core_Session::setStatus($status, ts('Added Contact to %1', array( - 1 => $groupName, - 'count' => $added, - 'plural' => 'Added Contacts to %1', - )), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts('Added Contact to %1', [ + 1 => $groupName, + 'count' => $added, + 'plural' => 'Added Contacts to %1', + ]), 'success', ['expires' => 0]); if ($this->_context === 'amtg') { CRM_Core_Session::singleton() diff --git a/CRM/Contact/Form/Task/AddToHousehold.php b/CRM/Contact/Form/Task/AddToHousehold.php index 72ab77fb7f14..c72e9a096522 100644 --- a/CRM/Contact/Form/Task/AddToHousehold.php +++ b/CRM/Contact/Form/Task/AddToHousehold.php @@ -1,9 +1,9 @@ addElement('text', 'name', ts('Find Target Household')); - - $this->add('select', 'relationship_type_id', ts('Relationship Type'), - array( - '' => ts('- select -'), - ) + - CRM_Contact_BAO_Relationship::getRelationType("Household"), TRUE - ); - - $searchRows = $this->get('searchRows'); - $searchCount = $this->get('searchCount'); - if ($searchRows) { - $checkBoxes = array(); - $checkFlag = 0; - foreach ($searchRows as $id => $row) { - if (!$checkFlag) { - $checkFlag = $id; - } - $checkBoxes[$id] = $this->createElement('radio', NULL, NULL, NULL, $id); - } - $this->addGroup($checkBoxes, 'contact_check'); - if ($checkFlag) { - $checkBoxes[$checkFlag]->setChecked(TRUE); - } - $this->assign('searchRows', $searchRows); - } - - $this->assign('searchCount', $searchCount); - $this->assign('searchDone', $this->get('searchDone')); - $this->assign('contact_type_display', ts('Household')); - $this->addElement('submit', $this->getButtonName('refresh'), ts('Search'), array('class' => 'crm-form-submit')); - $this->addElement('submit', $this->getButtonName('cancel'), ts('Cancel'), array('class' => 'crm-form-submit')); - - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Add to Household'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->set('contactType', 'Household'); + $this->assign('contactType', 'Household'); + parent::buildQuickForm(); } /** * Process the form after the input has been submitted and validated. */ public function postProcess() { - - // store the submitted values in an array - $this->params = $this->controller->exportValues($this->_name); - - $this->set('searchDone', 0); - if (!empty($_POST['_qf_AddToHousehold_refresh'])) { - $searchParams['contact_type'] = 'Household'; - $searchParams['rel_contact'] = $this->params['name']; - $this->search($this, $searchParams); - $this->set('searchDone', 1); - return; - } - - $this->addRelationships(); + parent::postProcess(); } } diff --git a/CRM/Contact/Form/Task/AddToIndividual.php b/CRM/Contact/Form/Task/AddToIndividual.php index df8da26684b2..f8f12aa4d907 100644 --- a/CRM/Contact/Form/Task/AddToIndividual.php +++ b/CRM/Contact/Form/Task/AddToIndividual.php @@ -1,9 +1,9 @@ addElement('text', 'name', ts('Find Target Individual')); - - $this->add('select', - 'relationship_type_id', - ts('Relationship Type'), - array( - '' => ts('- select -'), - ) + - CRM_Contact_BAO_Relationship::getRelationType("Individual"), TRUE - ); - - $searchRows = $this->get('searchRows'); - $searchCount = $this->get('searchCount'); - if ($searchRows) { - $checkBoxes = array(); - $chekFlag = 0; - foreach ($searchRows as $id => $row) { - if (!$chekFlag) { - $chekFlag = $id; - } - - $checkBoxes[$id] = $this->createElement('radio', NULL, NULL, NULL, $id); - } - - $this->addGroup($checkBoxes, 'contact_check'); - if ($chekFlag) { - $checkBoxes[$chekFlag]->setChecked(TRUE); - } - $this->assign('searchRows', $searchRows); - } - - $this->assign('searchCount', $searchCount); - $this->assign('searchDone', $this->get('searchDone')); - $this->assign('contact_type_display', ts('Individual')); - $this->addElement('submit', $this->getButtonName('refresh'), ts('Search'), array('class' => 'crm-form-submit')); - $this->addElement('submit', $this->getButtonName('cancel'), ts('Cancel'), array('class' => 'crm-form-submit')); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Add to Individual'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->set('contactType', 'Individual'); + $this->assign('contactType', 'Individual'); + parent::buildQuickForm(); } /** * Process the form after the input has been submitted and validated. */ public function postProcess() { - // store the submitted values in an array - $this->params = $this->controller->exportValues($this->_name); - - $this->set('searchDone', 0); - if (!empty($_POST['_qf_AddToIndividual_refresh'])) { - $searchParams['contact_type'] = 'Individual'; - $searchParams['rel_contact'] = $this->params['name']; - $this->search($this, $searchParams); - $this->set('searchDone', 1); - return; - } - - $this->addRelationships(); + parent::postProcess(); } } diff --git a/CRM/Contact/Form/Task/AddToOrganization.php b/CRM/Contact/Form/Task/AddToOrganization.php index 6f99e1fddf1d..089dbe24dbe7 100644 --- a/CRM/Contact/Form/Task/AddToOrganization.php +++ b/CRM/Contact/Form/Task/AddToOrganization.php @@ -1,9 +1,9 @@ addElement('text', 'name', ts('Find Target Organization')); - - $this->add('select', - 'relationship_type_id', - ts('Relationship Type'), - array( - '' => ts('- select -'), - ) + - CRM_Contact_BAO_Relationship::getRelationType("Organization"), TRUE - ); - - $searchRows = $this->get('searchRows'); - $searchCount = $this->get('searchCount'); - if ($searchRows) { - $checkBoxes = array(); - $chekFlag = 0; - foreach ($searchRows as $id => $row) { - if (!$chekFlag) { - $chekFlag = $id; - } - - $checkBoxes[$id] = $this->createElement('radio', NULL, NULL, NULL, $id); - } - - $this->addGroup($checkBoxes, 'contact_check'); - if ($chekFlag) { - $checkBoxes[$chekFlag]->setChecked(TRUE); - } - $this->assign('searchRows', $searchRows); - } - - $this->assign('searchCount', $searchCount); - $this->assign('searchDone', $this->get('searchDone')); - $this->assign('contact_type_display', ts('Organization')); - $this->addElement('submit', $this->getButtonName('refresh'), ts('Search'), array('class' => 'crm-form-submit')); - $this->addElement('submit', $this->getButtonName('cancel'), ts('Cancel'), array('class' => 'crm-form-submit')); - - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Add to Organization'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->set('contactType', 'Organization'); + $this->assign('contactType', 'Organization'); + parent::buildQuickForm(); } /** * Process the form after the input has been submitted and validated. */ public function postProcess() { - // store the submitted values in an array - $this->params = $this->controller->exportValues($this->_name); - - $this->set('searchDone', 0); - if (!empty($_POST['_qf_AddToOrganization_refresh'])) { - $searchParams['contact_type'] = 'Organization'; - $searchParams['rel_contact'] = $this->params['name']; - $this->search($this, $searchParams); - $this->set('searchDone', 1); - return; - } - - $this->addRelationships(); + parent::postProcess(); } } diff --git a/CRM/Contact/Form/Task/AddToParentClass.php b/CRM/Contact/Form/Task/AddToParentClass.php index 3274eddc801e..2895b7a7f83b 100644 --- a/CRM/Contact/Form/Task/AddToParentClass.php +++ b/CRM/Contact/Form/Task/AddToParentClass.php @@ -1,9 +1,9 @@ get('contactType'); + CRM_Utils_System::setTitle(ts('Add Contacts to %1', [1 => $contactType])); + $this->addElement('text', 'name', ts('Find Target %1', [1 => $contactType])); + + $this->add('select', + 'relationship_type_id', + ts('Relationship Type'), + [ + '' => ts('- select -'), + ] + + CRM_Contact_BAO_Relationship::getRelationType($contactType), TRUE + ); + + $searchRows = $this->get('searchRows'); + $searchCount = $this->get('searchCount'); + if ($searchRows) { + $checkBoxes = []; + $chekFlag = 0; + foreach ($searchRows as $id => $row) { + if (!$chekFlag) { + $chekFlag = $id; + } + + $checkBoxes[$id] = $this->createElement('radio', NULL, NULL, NULL, $id); + } + + $this->addGroup($checkBoxes, 'contact_check'); + if ($chekFlag) { + $checkBoxes[$chekFlag]->setChecked(TRUE); + } + $this->assign('searchRows', $searchRows); + } + + $this->assign('searchCount', $searchCount); + $this->assign('searchDone', $this->get('searchDone')); + $this->assign('contact_type_display', $contactType); + $this->addElement('submit', $this->getButtonName('refresh'), ts('Search'), ['class' => 'crm-form-submit']); + $this->addElement('submit', $this->getButtonName('cancel'), ts('Cancel'), ['class' => 'crm-form-submit']); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Add to %1', [1 => $contactType]), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + } + /** * Add relationships from form. */ @@ -54,10 +106,10 @@ public function addRelationships() { return; } $relationshipTypeParts = explode('_', $this->params['relationship_type_id']); - $params = array( + $params = [ 'relationship_type_id' => $relationshipTypeParts[0], 'is_active' => 1, - ); + ]; $secondaryRelationshipSide = $relationshipTypeParts[1]; $primaryRelationshipSide = $relationshipTypeParts[2]; $primaryFieldName = 'contact_id_' . $primaryRelationshipSide; @@ -73,33 +125,33 @@ public function addRelationships() { $relatedContactName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$primaryFieldName], 'display_name'); - $status = array( - ts('%count %2 %3 relationship created', array( + $status = [ + ts('%count %2 %3 relationship created', [ 'count' => $outcome['valid'], 'plural' => '%count %2 %3 relationships created', 2 => $relationshipLabel, 3 => $relatedContactName, - )), - ); + ]), + ]; if ($outcome['duplicate']) { - $status[] = ts('%count was skipped because the contact is already %2 %3', array( + $status[] = ts('%count was skipped because the contact is already %2 %3', [ 'count' => $outcome['duplicate'], 'plural' => '%count were skipped because the contacts are already %2 %3', 2 => $relationshipLabel, 3 => $relatedContactName, - )); + ]); } if ($outcome['invalid']) { - $status[] = ts('%count relationship was not created because the contact is not of the right type for this relationship', array( + $status[] = ts('%count relationship was not created because the contact is not of the right type for this relationship', [ 'count' => $outcome['invalid'], 'plural' => '%count relationships were not created because the contact is not of the right type for this relationship', - )); + ]); } $status = '
    • ' . implode('
    • ', $status) . '
    '; - CRM_Core_Session::setStatus($status, ts('Relationship created.', array( + CRM_Core_Session::setStatus($status, ts('Relationship created.', [ 'count' => $outcome['valid'], 'plural' => 'Relationships created.', - )), 'success', array('expires' => 0)); + ]), 'success', ['expires' => 0]); } @@ -112,20 +164,20 @@ public function addRelationships() { */ public function search(&$form, &$params) { //max records that will be listed - $searchValues = array(); + $searchValues = []; if (!empty($params['rel_contact'])) { if (isset($params['rel_contact_id']) && is_numeric($params['rel_contact_id']) ) { - $searchValues[] = array('contact_id', '=', $params['rel_contact_id'], 0, 1); + $searchValues[] = ['contact_id', '=', $params['rel_contact_id'], 0, 1]; } else { - $searchValues[] = array('sort_name', 'LIKE', $params['rel_contact'], 0, 1); + $searchValues[] = ['sort_name', 'LIKE', $params['rel_contact'], 0, 1]; } } $contactTypeAdded = FALSE; - $excludedContactIds = array(); + $excludedContactIds = []; if (isset($form->_contactId)) { $excludedContactIds[] = $form->_contactId; } @@ -148,18 +200,18 @@ public function search(&$form, &$params) { $form->set('contact_type', $type); $form->set('contact_sub_type', $subType); if ($type == 'Individual' || $type == 'Organization' || $type == 'Household') { - $searchValues[] = array('contact_type', '=', $type, 0, 0); + $searchValues[] = ['contact_type', '=', $type, 0, 0]; $contactTypeAdded = TRUE; } if ($subType) { - $searchValues[] = array('contact_sub_type', '=', $subType, 0, 0); + $searchValues[] = ['contact_sub_type', '=', $subType, 0, 0]; } } } if (!$contactTypeAdded && !empty($params['contact_type'])) { - $searchValues[] = array('contact_type', '=', $params['contact_type'], 0, 0); + $searchValues[] = ['contact_type', '=', $params['contact_type'], 0, 0]; } // get the count of contact @@ -172,7 +224,7 @@ public function search(&$form, &$params) { $result = $query->searchQuery(0, 50, NULL); $config = CRM_Core_Config::singleton(); - $searchRows = array(); + $searchRows = []; //variable is set if only one record is foun and that record already has relationship with the contact $duplicateRelationship = 0; @@ -210,4 +262,23 @@ public function search(&$form, &$params) { } } + /** + * Process the form after the input has been submitted and validated. + */ + public function postProcess() { + // store the submitted values in an array + $this->params = $this->controller->exportValues($this->_name); + $this->set('searchDone', 0); + $contactType = $this->get('contactType'); + + if (!empty($_POST["_qf_AddTo{$contactType}_refresh"])) { + $searchParams['contact_type'] = $contactType; + $searchParams['rel_contact'] = $this->params['name']; + $this->search($this, $searchParams); + $this->set('searchDone', 1); + return; + } + $this->addRelationships(); + } + } diff --git a/CRM/Contact/Form/Task/AddToTag.php b/CRM/Contact/Form/Task/AddToTag.php index 3ef04b3fc260..db80cfc11985 100644 --- a/CRM/Contact/Form/Task/AddToTag.php +++ b/CRM/Contact/Form/Task/AddToTag.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Task_AddToTag', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_AddToTag', 'formRule']); } /** @@ -82,7 +82,7 @@ public function addRules() { * @return array */ public static function formRule($form, $rule) { - $errors = array(); + $errors = []; if (empty($form['tag']) && empty($form['contact_taglist'])) { $errors['_qf_default'] = ts("Please select at least one tag."); } @@ -95,7 +95,7 @@ public static function formRule($form, $rule) { public function postProcess() { //get the submitted values in an array $params = $this->controller->exportValues($this->_name); - $contactTags = $tagList = array(); + $contactTags = $tagList = []; // check if contact tags exists if (!empty($params['tag'])) { @@ -132,22 +132,22 @@ public function postProcess() { // merge contact and taglist tags $allTags = CRM_Utils_Array::crmArrayMerge($contactTags, $tagList); - $this->_name = array(); + $this->_name = []; foreach ($allTags as $key => $dnc) { $this->_name[] = $this->_tags[$key]; list($total, $added, $notAdded) = CRM_Core_BAO_EntityTag::addEntitiesToTag($this->_contactIds, $key, 'civicrm_contact', FALSE); - $status = array(ts('%count contact tagged', array('count' => $added, 'plural' => '%count contacts tagged'))); + $status = [ts('%count contact tagged', ['count' => $added, 'plural' => '%count contacts tagged'])]; if ($notAdded) { - $status[] = ts('%count contact already had this tag', array( - 'count' => $notAdded, - 'plural' => '%count contacts already had this tag', - )); + $status[] = ts('%count contact already had this tag', [ + 'count' => $notAdded, + 'plural' => '%count contacts already had this tag', + ]); } $status = '
    • ' . implode('
    • ', $status) . '
    '; - CRM_Core_Session::setStatus($status, ts("Added Tag %1", array(1 => $this->_tags[$key])), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts("Added Tag %1", [1 => $this->_tags[$key]]), 'success', ['expires' => 0]); } } diff --git a/CRM/Contact/Form/Task/AlterPreferences.php b/CRM/Contact/Form/Task/AlterPreferences.php index ef487ae253ab..cd596f0c9822 100644 --- a/CRM/Contact/Form/Task/AlterPreferences.php +++ b/CRM/Contact/Form/Task/AlterPreferences.php @@ -1,9 +1,9 @@ addRadio('actionTypeOption', ts('actionTypeOption'), $options); @@ -57,7 +57,7 @@ public function buildQuickForm() { } public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_Task_AlterPreferences', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_AlterPreferences', 'formRule']); } /** @@ -68,7 +68,7 @@ public function addRules() { * the default array reference */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; $defaults['actionTypeOption'] = 0; return $defaults; @@ -81,7 +81,7 @@ public function setDefaultValues() { * @return array */ public static function formRule($form, $rule) { - $errors = array(); + $errors = []; if (empty($form['pref']) && empty($form['contact_taglist'])) { $errors['_qf_default'] = ts("Please select at least one privacy option."); } @@ -115,19 +115,19 @@ public function postProcess() { } // Status message $privacyOptions = CRM_Core_SelectValues::privacy(); - $status = array(); + $status = []; foreach ($privacyValues as $privacy_key => $privacy_value) { $label = $privacyOptions[$privacy_key]; - $status[] = $privacyValueNew ? ts("Added '%1'", array(1 => $label)) : ts("Removed '%1'", array(1 => $label)); + $status[] = $privacyValueNew ? ts("Added '%1'", [1 => $label]) : ts("Removed '%1'", [1 => $label]); } $status = '
    • ' . implode('
    • ', $status) . '
    '; if ($count > 1) { - $title = ts('%1 Contacts Updated', array(1 => $count)); + $title = ts('%1 Contacts Updated', [1 => $count]); } else { $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contact_id, 'display_name'); - $title = ts('%1 Updated', array(1 => $name)); + $title = ts('%1 Updated', [1 => $name]); } CRM_Core_Session::setStatus($status, $title, 'success'); diff --git a/CRM/Contact/Form/Task/Batch.php b/CRM/Contact/Form/Task/Batch.php index ca607f04d6ab..d0bc8ac47756 100644 --- a/CRM/Contact/Form/Task/Batch.php +++ b/CRM/Contact/Form/Task/Batch.php @@ -1,9 +1,9 @@ _fields as $name => $field) { if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) && in_array($this->_fields[$name]['html_type'], $removehtmlTypes) @@ -101,25 +105,24 @@ public function buildQuickForm() { //FIX ME: phone ext field is added at the end and it gets removed because of below code //$this->_fields = array_slice($this->_fields, 0, $this->_maxFields); - $this->addButtons(array( - array( - 'type' => 'submit', - 'name' => ts('Update Contact(s)'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'submit', + 'name' => ts('Update Contact(s)'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); $this->assign('profileTitle', $this->_title); $this->assign('componentIds', $this->_contactIds); // if below fields are missing we should not reset sort name / display name // CRM-6794 - $preserveDefaultsArray = array( + $preserveDefaultsArray = [ 'first_name', 'last_name', 'middle_name', @@ -127,7 +130,7 @@ public function buildQuickForm() { 'prefix_id', 'suffix_id', 'household_name', - ); + ]; foreach ($this->_contactIds as $contactId) { $profileFields = $this->_fields; @@ -147,11 +150,11 @@ public function buildQuickForm() { $buttonName = $this->controller->getButtonName('submit'); if ($suppressFields && $buttonName != '_qf_BatchUpdateProfile_next') { - CRM_Core_Session::setStatus(ts("File or Autocomplete-Select type field(s) in the selected profile are not supported for Update multiple contacts."), ts('Some Fields Excluded'), 'info'); + CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple contacts."), ts('Some Fields Excluded'), 'info'); } $this->addDefaultButtons(ts('Update Contacts')); - $this->addFormRule(array('CRM_Contact_Form_Task_Batch', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_Batch', 'formRule']); } /** @@ -165,9 +168,9 @@ public function setDefaultValues() { return NULL; } - $defaults = $sortName = array(); + $defaults = $sortName = []; foreach ($this->_contactIds as $contactId) { - $details[$contactId] = array(); + $details[$contactId] = []; //build sortname $sortName[$contactId] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', @@ -193,8 +196,8 @@ public function setDefaultValues() { * true if no errors, else array of errors */ public static function formRule($fields) { - $errors = array(); - $externalIdentifiers = array(); + $errors = []; + $externalIdentifiers = []; foreach ($fields['field'] as $componentId => $field) { foreach ($field as $fieldName => $fieldValue) { if ($fieldName == 'external_identifier') { @@ -217,6 +220,9 @@ public static function formRule($fields) { public function postProcess() { $params = $this->exportValues(); + // @todo extract submit functions & + // extend CRM_Event_Form_Task_BatchTest::testSubmit with a data provider to test + // handling of custom data, specifically checkbox fields. $ufGroupId = $this->get('ufGroupId'); $notify = NULL; $inValidSubtypeCnt = 0; @@ -245,10 +251,10 @@ public function postProcess() { CRM_Core_Session::setStatus('', ts("Updates Saved"), 'success'); if ($inValidSubtypeCnt) { - CRM_Core_Session::setStatus(ts('Contact Subtype field of 1 contact has not been updated.', array( - 'plural' => 'Contact Subtype field of %count contacts has not been updated.', - 'count' => $inValidSubtypeCnt, - )), ts('Invalid Subtype')); + CRM_Core_Session::setStatus(ts('Contact Subtype field of 1 contact has not been updated.', [ + 'plural' => 'Contact Subtype field of %count contacts has not been updated.', + 'count' => $inValidSubtypeCnt, + ]), ts('Invalid Subtype')); } } @@ -286,7 +292,7 @@ public static function parseStreetAddress(&$contactValues, &$form) { return; } - $allParseValues = array(); + $allParseValues = []; foreach ($contactValues as $key => $value) { if (strpos($key, $addressFldKey) !== FALSE) { $locTypeId = substr($key, strlen($addressFldKey) + 1); diff --git a/CRM/Contact/Form/Task/Delete.php b/CRM/Contact/Form/Task/Delete.php index 33b4c4bb321c..b1f0160902ae 100644 --- a/CRM/Contact/Form/Task/Delete.php +++ b/CRM/Contact/Form/Task/Delete.php @@ -1,9 +1,9 @@ _contactIds = array($cid); + $this->_contactIds = [$cid]; $this->_single = TRUE; $this->assign('totalSelectedContacts', 1); } @@ -100,7 +101,7 @@ public function preProcess() { $this->_sharedAddressMessage = $this->get('sharedAddressMessage'); if (!$this->_restore && !$this->_sharedAddressMessage) { // we check for each contact for shared contact address - $sharedContactList = array(); + $sharedContactList = []; $sharedAddressCount = 0; foreach ($this->_contactIds as $contactId) { // check if a contact that is being deleted has any shared addresses @@ -114,25 +115,25 @@ public function preProcess() { } } - $this->_sharedAddressMessage = array( + $this->_sharedAddressMessage = [ 'count' => $sharedAddressCount, 'contactList' => $sharedContactList, - ); + ]; if ($sharedAddressCount > 0) { if (count($this->_contactIds) > 1) { // more than one contact deleted - $message = ts('One of the selected contacts has an address record that is shared with 1 other contact.', array( - 'plural' => 'One or more selected contacts have address records which are shared with %count other contacts.', - 'count' => $sharedAddressCount, - )); + $message = ts('One of the selected contacts has an address record that is shared with 1 other contact.', [ + 'plural' => 'One or more selected contacts have address records which are shared with %count other contacts.', + 'count' => $sharedAddressCount, + ]); } else { // only one contact deleted - $message = ts('This contact has an address record which is shared with 1 other contact.', array( - 'plural' => 'This contact has an address record which is shared with %count other contacts.', - 'count' => $sharedAddressCount, - )); + $message = ts('This contact has an address record which is shared with 1 other contact.', [ + 'plural' => 'This contact has an address record which is shared with %count other contacts.', + 'count' => $sharedAddressCount, + ]); } CRM_Core_Session::setStatus($message . ' ' . ts('Shared addresses will not be removed or altered but will no longer be shared.'), ts('Shared Addesses Owner')); } @@ -150,7 +151,7 @@ public function buildQuickForm() { if ($this->_single) { // also fix the user context stack in case the user hits cancel - $context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'basic'); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'basic'); if ($context == 'search' && CRM_Utils_Rule::qfKey($this->_searchKey)) { $urlParams = "&context=$context&key=$this->_searchKey"; } @@ -168,7 +169,7 @@ public function buildQuickForm() { $this->addDefaultButtons($label, 'done'); } - $this->addFormRule(array('CRM_Contact_Form_Task_Delete', 'formRule'), $this); + $this->addFormRule(['CRM_Contact_Form_Task_Delete', 'formRule'], $this); } /** @@ -186,7 +187,7 @@ public function buildQuickForm() { */ public static function formRule($fields, $files, $self) { // CRM-12929 - $error = array(); + $error = []; if ($self->_skipUndelete) { CRM_Financial_BAO_FinancialItem::checkContactPresent($self->_contactIds, $error); } @@ -200,7 +201,7 @@ public function postProcess() { $session = CRM_Core_Session::singleton(); $currentUserId = $session->get('userID'); - $context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'basic'); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'basic'); $urlParams = 'force=1'; $urlString = "civicrm/contact/search/$context"; @@ -222,15 +223,15 @@ public function postProcess() { // Delete/Restore Contacts. Report errors. $deleted = 0; - $not_deleted = array(); + $not_deleted = []; foreach ($this->_contactIds as $cid) { $name = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $cid, 'display_name'); if (CRM_Contact_BAO_Contact::checkDomainContact($cid)) { - $session->setStatus(ts("'%1' cannot be deleted because the information is used for special system purposes.", array(1 => $name)), 'Cannot Delete Domain Contact', 'error'); + $session->setStatus(ts("'%1' cannot be deleted because the information is used for special system purposes.", [1 => $name]), 'Cannot Delete Domain Contact', 'error'); continue; } if ($currentUserId == $cid && !$this->_restore) { - $session->setStatus(ts("You are currently logged in as '%1'. You cannot delete yourself.", array(1 => $name)), 'Unable To Delete', 'error'); + $session->setStatus(ts("You are currently logged in as '%1'. You cannot delete yourself.", [1 => $name]), 'Unable To Delete', 'error'); continue; } if (CRM_Contact_BAO_Contact::deleteContact($cid, $this->_restore, $this->_skipUndelete)) { @@ -245,25 +246,25 @@ public function postProcess() { $title = ts('Deleted'); if ($this->_restore) { $title = ts('Restored'); - $status = ts('%1 has been restored from the trash.', array( - 1 => $name, - 'plural' => '%count contacts restored from trash.', - 'count' => $deleted, - )); + $status = ts('%1 has been restored from the trash.', [ + 1 => $name, + 'plural' => '%count contacts restored from trash.', + 'count' => $deleted, + ]); } elseif ($this->_skipUndelete) { - $status = ts('%1 has been permanently deleted.', array( - 1 => $name, - 'plural' => '%count contacts permanently deleted.', - 'count' => $deleted, - )); + $status = ts('%1 has been permanently deleted.', [ + 1 => $name, + 'plural' => '%count contacts permanently deleted.', + 'count' => $deleted, + ]); } else { - $status = ts('%1 has been moved to the trash.', array( - 1 => $name, - 'plural' => '%count contacts moved to trash.', - 'count' => $deleted, - )); + $status = ts('%1 has been moved to the trash.', [ + 1 => $name, + 'plural' => '%count contacts moved to trash.', + 'count' => $deleted, + ]); } $session->setStatus($status, $title, 'success'); } @@ -283,7 +284,7 @@ public function postProcess() { } $message .= '
    • ' . implode('
    • ', $this->_sharedAddressMessage['contactList']) . '
    '; - $session->setStatus($message, ts('Shared Addesses Owner Deleted'), 'info', array('expires' => 0)); + $session->setStatus($message, ts('Shared Addesses Owner Deleted'), 'info', ['expires' => 0]); $this->set('sharedAddressMessage', NULL); } diff --git a/CRM/Contact/Form/Task/Email.php b/CRM/Contact/Form/Task/Email.php index 42259eec6bb3..60c38d3e7160 100644 --- a/CRM/Contact/Form/Task/Email.php +++ b/CRM/Contact/Form/Task/Email.php @@ -1,9 +1,9 @@ _caseId = CRM_Utils_Request::retrieve('caseid', 'String', $this, FALSE); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $cid = CRM_Utils_Request::retrieve('cid', 'String', $this, FALSE); // Allow request to specify email id rather than contact id $toEmailId = CRM_Utils_Request::retrieve('email_id', 'String', $this); if ($toEmailId) { - $toEmail = civicrm_api('email', 'getsingle', array('version' => 3, 'id' => $toEmailId)); + $toEmail = civicrm_api('email', 'getsingle', ['version' => 3, 'id' => $toEmailId]); if (!empty($toEmail['email']) && !empty($toEmail['contact_id'])) { $this->_toEmail = $toEmail; } @@ -115,7 +115,7 @@ public function preProcess() { if ($cid) { $cid = explode(',', $cid); - $displayName = array(); + $displayName = []; foreach ($cid as $val) { $displayName[] = CRM_Contact_BAO_Contact::displayName($val); @@ -132,11 +132,6 @@ public function preProcess() { parent::preProcess(); } - //early prevent, CRM-6209 - if (count($this->_contactIds) > CRM_Contact_Form_Task_EmailCommon::MAX_EMAILS_KILL_SWITCH) { - CRM_Core_Error::statusBounce(ts('Please do not use this task to send a lot of emails (greater than %1). We recommend using CiviMail instead.', array(1 => CRM_Contact_Form_Task_EmailCommon::MAX_EMAILS_KILL_SWITCH))); - } - $this->assign('single', $this->_single); if (CRM_Core_Permission::check('administer CiviCRM')) { $this->assign('isAdmin', 1); @@ -168,6 +163,13 @@ public function postProcess() { */ public function listTokens() { $tokens = CRM_Core_SelectValues::contactTokens(); + + if (isset($this->_caseId) || isset($this->_caseIds)) { + // For a single case, list tokens relevant for only that case type + $caseTypeId = isset($this->_caseId) ? CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $this->_caseId, 'case_type_id') : NULL; + $tokens += CRM_Core_SelectValues::caseTokens($caseTypeId); + } + return $tokens; } diff --git a/CRM/Contact/Form/Task/EmailCommon.php b/CRM/Contact/Form/Task/EmailCommon.php index 8757cac0d2bb..5006f9d5321c 100644 --- a/CRM/Contact/Form/Task/EmailCommon.php +++ b/CRM/Contact/Form/Task/EmailCommon.php @@ -1,9 +1,9 @@ _single = TRUE; } - $form->_emails = $emails = array(); - - $contactID = CRM_Core_Session::singleton()->getLoggedInContactID(); - $fromDisplayName = CRM_Core_Session::singleton()->getLoggedInContactDisplayName(); - - $form->_contactIds = array($contactID); - $contactEmails = CRM_Core_BAO_Email::allEmails($contactID); + $form->_emails = array(); - $form->_onHold = array(); + // @TODO remove these line and to it somewhere more appropriate. Currently some classes (e.g Case + // are having to re-write contactIds afterwards due to this inappropriate variable setting + // If we don't have any contact IDs, use the logged in contact ID + $form->_contactIds = $form->_contactIds ?: [CRM_Core_Session::getLoggedInContactID()]; - foreach ($contactEmails as $emailId => $item) { - $email = $item['email']; - if (!$email && (count($emails) < 1)) { - // set it if no emails are present at all - $form->_noEmails = TRUE; - } - else { - if ($email) { - if (in_array($email, $emails)) { - // CRM-3624 - continue; - } + $fromEmailValues = CRM_Core_BAO_Email::getFromEmail(); - $emails[$emailId] = '"' . $fromDisplayName . '" <' . $email . '> '; - $form->_onHold[$emailId] = $item['on_hold']; - $form->_noEmails = FALSE; - } - } - if (!empty($email)) { - $form->_emails[$emailId] = $emails[$emailId]; - $emails[$emailId] .= $item['locationType']; - - if ($item['is_primary']) { - $emails[$emailId] .= ' ' . ts('(preferred)'); - } - $emails[$emailId] = htmlspecialchars($emails[$emailId]); - } + $form->_noEmails = FALSE; + if (empty($fromEmailValues)) { + $form->_noEmails = TRUE; } - $form->assign('noEmails', $form->_noEmails); if ($bounce) { if ($form->_noEmails) { - CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address')); + CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address and no from addresses have been configured.')); } } - // now add domain from addresses - $domainEmails = self::domainEmails(); - foreach ($domainEmails as $domainEmail => $email) { - $form->_emails[$domainEmail] = $domainEmail; + $form->_emails = $fromEmailValues; + $defaults = array(); + $form->_fromEmails = $fromEmailValues; + if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) { + $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE)); } - $form->_fromEmails = CRM_Utils_Array::crmArrayMerge($emails, $domainEmails); - $form->_fromEmails = array_filter($form->_fromEmails); if (is_numeric(key($form->_fromEmails))) { // Add signature $defaultEmail = civicrm_api3('email', 'getsingle', array('id' => key($form->_fromEmails))); @@ -136,8 +104,8 @@ public static function preProcessFromAddress(&$form, $bounce = TRUE) { if (!empty($defaultEmail['signature_text'])) { $defaults['text_message'] = "\n\n--\n" . $defaultEmail['signature_text']; } - $form->setDefaults($defaults); } + $form->setDefaults($defaults); } /** @@ -156,6 +124,7 @@ public static function buildQuickForm(&$form) { if (count($form->_contactIds) > 1) { $form->_single = FALSE; } + CRM_Contact_Form_Task_EmailCommon::bounceIfSimpleMailLimitExceeded(count($form->_contactIds)); $emailAttributes = array( 'class' => 'huge', @@ -287,7 +256,7 @@ public static function buildQuickForm(&$form) { $form->add('text', 'subject', ts('Subject'), 'size=50 maxlength=254', TRUE); - $form->add('select', 'fromEmailAddress', ts('From'), $form->_fromEmails, TRUE, array('class' => 'crm-select2 huge')); + $form->add('select', 'from_email_address', ts('From'), $form->_fromEmails, TRUE); CRM_Mailing_BAO_Mailing::commonCompose($form); @@ -347,7 +316,7 @@ public static function buildQuickForm(&$form) { ); //add followup date - $form->addDateTime('followup_date', ts('in'), FALSE, array('formatType' => 'activityDateTime')); + $form->add('datepicker', 'followup_date', ts('in')); foreach ($fields as $field => $values) { if (!empty($fields[$field])) { @@ -409,11 +378,7 @@ public static function formRule($fields, $dontCare, $self) { * @param CRM_Core_Form $form */ public static function postProcess(&$form) { - if (count($form->_contactIds) > self::MAX_EMAILS_KILL_SWITCH) { - CRM_Core_Error::fatal(ts('Please do not use this task to send a lot of emails (greater than %1). We recommend using CiviMail instead.', - array(1 => self::MAX_EMAILS_KILL_SWITCH) - )); - } + self::bounceIfSimpleMailLimitExceeded(count($form->_contactIds)); // check and ensure that $formValues = $form->controller->exportValues($form->getName()); @@ -431,7 +396,11 @@ public static function postProcess(&$form) { public static function submit(&$form, $formValues) { self::saveMessageTemplate($formValues); - $from = CRM_Utils_Array::value($formValues['fromEmailAddress'], $form->_emails); + $from = CRM_Utils_Array::value('from_email_address', $formValues); + // dev/core#357 User Emails are keyed by their id so that the Signature is able to be added + // If we have had a contact email used here the value returned from the line above will be the + // numerical key where as $from for use in the sendEmail in Activity needs to be of format of "To Name" + $from = CRM_Utils_Mail::formatFromAddress($from); $subject = $formValues['subject']; // CRM-13378: Append CC and BCC information at the end of Activity Details and format cc and bcc fields @@ -533,7 +502,6 @@ public static function submit(&$form, $formValues) { $params['followup_activity_type_id'] = $formValues['followup_activity_type_id']; $params['followup_activity_subject'] = $formValues['followup_activity_subject']; $params['followup_date'] = $formValues['followup_date']; - $params['followup_date_time'] = $formValues['followup_date_time']; $params['target_contact_id'] = $form->_contactIds; $params['followup_assignee_contact_id'] = explode(',', $formValues['followup_assignee_contact_id']); $followupActivity = CRM_Activity_BAO_Activity::createFollowupActivity($activityId, $params); @@ -558,9 +526,9 @@ public static function submit(&$form, $formValues) { $count_success = count($form->_toContactDetails); CRM_Core_Session::setStatus(ts('One message was sent successfully. ', array( - 'plural' => '%count messages were sent successfully. ', - 'count' => $count_success, - )) . $followupStatus, ts('Message Sent', array('plural' => 'Messages Sent', 'count' => $count_success)), 'success'); + 'plural' => '%count messages were sent successfully. ', + 'count' => $count_success, + )) . $followupStatus, ts('Message Sent', array('plural' => 'Messages Sent', 'count' => $count_success)), 'success'); } // Display the name and number of contacts for those email is not sent. @@ -579,9 +547,9 @@ public static function submit(&$form, $formValues) { } $status = '(' . ts('because no email address on file or communication preferences specify DO NOT EMAIL or Contact is deceased or Primary email address is On Hold') . ')
    • ' . implode('
    • ', $not_sent) . '
    '; CRM_Core_Session::setStatus($status, ts('One Message Not Sent', array( - 'count' => count($emailsNotSent), - 'plural' => '%count Messages Not Sent', - )), 'info'); + 'count' => count($emailsNotSent), + 'plural' => '%count Messages Not Sent', + )), 'info'); } if (isset($form->_caseId)) { @@ -626,4 +594,19 @@ protected static function saveMessageTemplate($formValues) { } } + /** + * Bounce if there are more emails than permitted. + * + * @param int $count + * The number of emails the user is attempting to send + */ + public static function bounceIfSimpleMailLimitExceeded($count) { + $limit = Civi::settings()->get('simple_mail_limit'); + if ($count > $limit) { + CRM_Core_Error::statusBounce(ts('Please do not use this task to send a lot of emails (greater than %1). Many countries have legal requirements when sending bulk emails and the CiviMail framework has opt out functionality and domain tokens to help meet these.', + array(1 => $limit) + )); + } + } + } diff --git a/CRM/Contact/Form/Task/HookSample.php b/CRM/Contact/Form/Task/HookSample.php index 76fc66491a2f..a93a92a7ed2d 100644 --- a/CRM/Contact/Form/Task/HookSample.php +++ b/CRM/Contact/Form/Task/HookSample.php @@ -1,9 +1,9 @@ fetch()) { - $rows[] = array( + $rows[] = [ 'id' => $dao->contact_id, 'name' => $dao->name, 'contact_type' => $dao->contact_type, 'email' => $dao->email, - ); + ]; } $this->assign('rows', $rows); diff --git a/CRM/Contact/Form/Task/Label.php b/CRM/Contact/Form/Task/Label.php index 24e1dce7fe84..674d1d1ad310 100644 --- a/CRM/Contact/Form/Task/Label.php +++ b/CRM/Contact/Form/Task/Label.php @@ -1,9 +1,9 @@ add('select', 'label_name', ts('Select Label'), array('' => ts('- select label -')) + $label, TRUE); + $form->add('select', 'label_name', ts('Select Label'), ['' => ts('- select label -')] + $label, TRUE); // add select for Location Type $form->addElement('select', 'location_type_id', ts('Select Location'), - array( + [ '' => ts('Primary'), - ) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'), TRUE + ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'), TRUE ); // checkbox for SKIP contacts with Do Not Mail privacy option @@ -77,17 +77,17 @@ public static function buildLabelForm($form) { $form->add('checkbox', 'merge_same_address', ts('Merge labels for contacts with the same address'), NULL); $form->add('checkbox', 'merge_same_household', ts('Merge labels for contacts belonging to the same household'), NULL); - $form->addButtons(array( - array( + $form->addButtons([ + [ 'type' => 'submit', 'name' => ts('Make Mailing Labels'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Done'), - ), - )); + ], + ]); } /** @@ -97,7 +97,7 @@ public static function buildLabelForm($form) { * array of default values */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; $format = CRM_Core_BAO_LabelFormat::getDefaultValues(); $defaults['label_name'] = CRM_Utils_Array::value('name', $format); $defaults['do_not_mail'] = 1; @@ -126,10 +126,10 @@ public function postProcess() { } //build the returnproperties - $returnProperties = array('display_name' => 1, 'contact_type' => 1, 'prefix_id' => 1); + $returnProperties = ['display_name' => 1, 'contact_type' => 1, 'prefix_id' => 1]; $mailingFormat = Civi::settings()->get('mailing_format'); - $mailingFormatProperties = array(); + $mailingFormatProperties = []; if ($mailingFormat) { $mailingFormatProperties = CRM_Utils_Token::getReturnProperties($mailingFormat); $returnProperties = array_merge($returnProperties, $mailingFormatProperties); @@ -139,7 +139,7 @@ public function postProcess() { unset($mailingFormatProperties['addressee']); } - $customFormatProperties = array(); + $customFormatProperties = []; if (stristr($mailingFormat, 'custom_')) { foreach ($mailingFormatProperties as $token => $true) { if (substr($token, 0, 7) == 'custom_') { @@ -172,38 +172,38 @@ public function postProcess() { } //get the contacts information - $params = array(); + $params = []; if (!empty($fv['location_type_id'])) { $locType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $locName = $locType[$fv['location_type_id']]; - $location = array('location' => array("{$locName}" => $address)); + $location = ['location' => ["{$locName}" => $address]]; $returnProperties = array_merge($returnProperties, $location); - $params[] = array('location_type', '=', array(1 => $fv['location_type_id']), 0, 0); + $params[] = ['location_type', '=', [1 => $fv['location_type_id']], 0, 0]; } else { $returnProperties = array_merge($returnProperties, $address); } - $rows = array(); + $rows = []; foreach ($this->_contactIds as $key => $contactID) { - $params[] = array( + $params[] = [ CRM_Core_Form::CB_PREFIX . $contactID, '=', 1, 0, 0, - ); + ]; } // fix for CRM-2651 if (!empty($fv['do_not_mail'])) { - $params[] = array('do_not_mail', '=', 0, 0, 0); + $params[] = ['do_not_mail', '=', 0, 0, 0]; } // fix for CRM-2613 - $params[] = array('is_deceased', '=', 0, 0, 0); + $params[] = ['is_deceased', '=', 0, 0, 0]; - $custom = array(); + $custom = []; foreach ($returnProperties as $name => $dontCare) { $cfID = CRM_Core_BAO_CustomField::getKeyID($name); if ($cfID) { @@ -226,9 +226,9 @@ public function postProcess() { 'CRM_Contact_Form_Task_Label' ); - $tokens = array(); + $tokens = []; CRM_Utils_Hook::tokens($tokens); - $tokenFields = array(); + $tokenFields = []; foreach ($tokens as $category => $catTokens) { foreach ($catTokens as $token => $tokenName) { $tokenFields[] = $token; @@ -269,8 +269,8 @@ public function postProcess() { $rows[$value][$field] = $fieldValue; } - $valuesothers = array(); - $paramsothers = array('contact_id' => $value); + $valuesothers = []; + $paramsothers = ['contact_id' => $value]; $valuesothers = CRM_Core_BAO_Location::getValues($paramsothers, $valuesothers); if (!empty($fv['location_type_id'])) { foreach ($valuesothers as $vals) { @@ -278,12 +278,12 @@ public function postProcess() { CRM_Utils_Array::value('location_type_id', $fv) ) { foreach ($vals as $k => $v) { - if (in_array($k, array( + if (in_array($k, [ 'email', 'phone', 'im', 'openid', - ))) { + ])) { if ($k == 'im') { $rows[$value][$k] = $v['1']['name']; } @@ -326,14 +326,14 @@ public function postProcess() { if ($commMethods = CRM_Utils_Array::value('preferred_communication_method', $row)) { $val = array_filter(explode(CRM_Core_DAO::VALUE_SEPARATOR, $commMethods)); $comm = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method'); - $temp = array(); + $temp = []; foreach ($val as $vals) { $temp[] = $comm[$vals]; } $row['preferred_communication_method'] = implode(', ', $temp); } $row['id'] = $id; - $formatted = CRM_Utils_Address::format($row, 'mailing_format', FALSE, TRUE, $individualFormat, $tokenFields); + $formatted = CRM_Utils_Address::format($row, 'mailing_format', FALSE, TRUE, $tokenFields); // CRM-2211: UFPDF doesn't have bidi support; use the PECL fribidi package to fix it. // On Ubuntu (possibly Debian?) be aware of http://pecl.php.net/bugs/bug.php?id=12366 @@ -346,12 +346,12 @@ public function postProcess() { } $formatted = implode("\n", $lines); } - $rows[$id] = array($formatted); + $rows[$id] = [$formatted]; } //call function to create labels self::createLabel($rows, $fv['label_name']); - CRM_Utils_System::civiExit(1); + CRM_Utils_System::civiExit(); } /** diff --git a/CRM/Contact/Form/Task/LabelCommon.php b/CRM/Contact/Form/Task/LabelCommon.php index 57376f885c39..74bdc83cb185 100644 --- a/CRM/Contact/Form/Task/LabelCommon.php +++ b/CRM/Contact/Form/Task/LabelCommon.php @@ -1,9 +1,9 @@ Output($fileName, 'D'); } - /** * Get the rows for the labels. * @@ -80,21 +79,21 @@ public static function createLabel(&$contactRows, &$format, $fileName = 'Mailing */ public static function getRows($contactIDs, $locationTypeID, $respectDoNotMail, $mergeSameAddress, $mergeSameHousehold) { $locName = NULL; - $rows = array(); + $rows = []; //get the address format sequence from the config file $addressReturnProperties = CRM_Contact_Form_Task_LabelCommon::getAddressReturnProperties(); //build the return properties - $returnProperties = array('display_name' => 1, 'contact_type' => 1, 'prefix_id' => 1); + $returnProperties = ['display_name' => 1, 'contact_type' => 1, 'prefix_id' => 1]; $mailingFormat = Civi::settings()->get('mailing_format'); - $mailingFormatProperties = array(); + $mailingFormatProperties = []; if ($mailingFormat) { $mailingFormatProperties = CRM_Utils_Token::getReturnProperties($mailingFormat); $returnProperties = array_merge($returnProperties, $mailingFormatProperties); } - $customFormatProperties = array(); + $customFormatProperties = []; if (stristr($mailingFormat, 'custom_')) { foreach ($mailingFormatProperties as $token => $true) { if (substr($token, 0, 7) == 'custom_') { @@ -113,30 +112,30 @@ public static function getRows($contactIDs, $locationTypeID, $respectDoNotMail, } //get the contacts information - $params = $custom = array(); + $params = $custom = []; foreach ($contactIDs as $key => $contactID) { - $params[] = array( + $params[] = [ CRM_Core_Form::CB_PREFIX . $contactID, '=', 1, 0, 0, - ); + ]; } // fix for CRM-2651 if (!empty($respectDoNotMail['do_not_mail'])) { - $params[] = array('do_not_mail', '=', 0, 0, 0); + $params[] = ['do_not_mail', '=', 0, 0, 0]; } // fix for CRM-2613 - $params[] = array('is_deceased', '=', 0, 0, 0); + $params[] = ['is_deceased', '=', 0, 0, 0]; if ($locationTypeID) { $locType = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $locName = $locType[$locationTypeID]; - $location = array('location' => array("{$locName}" => $addressReturnProperties)); + $location = ['location' => ["{$locName}" => $addressReturnProperties]]; $returnProperties = array_merge($returnProperties, $location); - $params[] = array('location_type', '=', array($locationTypeID => 1), 0, 0); + $params[] = ['location_type', '=', [$locationTypeID => 1], 0, 0]; } else { $returnProperties = array_merge($returnProperties, $addressReturnProperties); @@ -193,8 +192,8 @@ public static function getRows($contactIDs, $locationTypeID, $respectDoNotMail, $rows[$value][$field] = $fieldValue; } - $valuesothers = array(); - $paramsothers = array('contact_id' => $value); + $valuesothers = []; + $paramsothers = ['contact_id' => $value]; $valuesothers = CRM_Core_BAO_Location::getValues($paramsothers, $valuesothers); if ($locationTypeID) { foreach ($valuesothers as $vals) { @@ -202,12 +201,12 @@ public static function getRows($contactIDs, $locationTypeID, $respectDoNotMail, $locationTypeID ) { foreach ($vals as $k => $v) { - if (in_array($k, array( + if (in_array($k, [ 'email', 'phone', 'im', 'openid', - ))) { + ])) { if ($k == 'im') { $rows[$value][$k] = $v['1']['name']; } @@ -240,7 +239,7 @@ public static function getRows($contactIDs, $locationTypeID, $respectDoNotMail, } } // sigh couldn't extract out tokenfields yet - return array($rows, $tokenFields); + return [$rows, $tokenFields]; } /** @@ -269,7 +268,7 @@ public static function getAddressReturnProperties() { */ public static function getTokenData(&$contacts) { $mailingFormat = Civi::settings()->get('mailing_format'); - $tokens = $tokenFields = array(); + $tokens = $tokenFields = []; $messageToken = CRM_Utils_Token::getTokens($mailingFormat); // also get all token values @@ -298,8 +297,8 @@ public static function getTokenData(&$contacts) { */ public function mergeSameHousehold(&$rows) { // group selected contacts by type - $individuals = array(); - $households = array(); + $individuals = []; + $households = []; foreach ($rows as $contact_id => $row) { if ($row['contact_type'] == 'Household') { $households[$contact_id] = $row; diff --git a/CRM/Contact/Form/Task/Map.php b/CRM/Contact/Form/Task/Map.php index 7f0bc3ad01e2..89c52caf3b33 100644 --- a/CRM/Contact/Form/Task/Map.php +++ b/CRM/Contact/Form/Task/Map.php @@ -1,9 +1,9 @@ assign('profileGID', $profileGID); - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $type = 'Contact'; if ($cid) { - $ids = array($cid); + $ids = [$cid]; $this->_single = TRUE; if ($profileGID) { // this does a check and ensures that the user has permission on this profile @@ -113,14 +113,13 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } /** @@ -221,14 +220,14 @@ public static function createMapXML($ids, $locationId, &$page, $addBreadCrumb, $ } } - $center = array( + $center = [ 'lat' => (float ) $sumLat / count($locations), 'lng' => (float ) $sumLng / count($locations), - ); - $span = array( + ]; + $span = [ 'lat' => (float ) ($maxLat - $minLat), 'lng' => (float ) ($maxLng - $minLng), - ); + ]; $page->assign_by_ref('center', $center); $page->assign_by_ref('span', $span); } diff --git a/CRM/Contact/Form/Task/Map/Event.php b/CRM/Contact/Form/Task/Map/Event.php index 257fc1b1a843..496e172f6763 100644 --- a/CRM/Contact/Form/Task/Map/Event.php +++ b/CRM/Contact/Form/Task/Map/Event.php @@ -1,9 +1,9 @@ assign('single', FALSE); $this->assign('skipLocationType', TRUE); + $is_public = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $ids, 'is_public'); + if ($is_public == 0) { + CRM_Utils_System::addHTMLHead(''); + } } /** diff --git a/CRM/Contact/Form/Task/Merge.php b/CRM/Contact/Form/Task/Merge.php index d0f0abd328b1..80359da7af4a 100644 --- a/CRM/Contact/Form/Task/Merge.php +++ b/CRM/Contact/Form/Task/Merge.php @@ -1,9 +1,9 @@ _contactIds)) { $contactIds = array_unique($this->_contactIds); } @@ -52,7 +51,7 @@ public function preProcess() { } // do check for same contact type. - $contactTypes = array(); + $contactTypes = []; if (!$statusMsg) { $sql = "SELECT contact_type FROM civicrm_contact WHERE id IN (" . implode(',', $contactIds) . ")"; $contact = CRM_Core_DAO::executeQuery($sql); diff --git a/CRM/Contact/Form/Task/PDF.php b/CRM/Contact/Form/Task/PDF.php index 217cf7af1121..67f42d2c30f3 100644 --- a/CRM/Contact/Form/Task/PDF.php +++ b/CRM/Contact/Form/Task/PDF.php @@ -1,9 +1,9 @@ _caseId = CRM_Utils_Request::retrieve('caseid', 'Positive', $this, FALSE); + $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'CommaSeparatedIntegers', $this, FALSE); + if (!empty($this->_caseId) && strpos($this->_caseId, ',')) { + $this->_caseIds = explode(',', $this->_caseId); + unset($this->_caseId); + } // retrieve contact ID if this is 'single' mode - $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE); + $cid = CRM_Utils_Request::retrieve('cid', 'CommaSeparatedIntegers', $this, FALSE); if ($cid) { // this is true in non-search context / single mode @@ -73,7 +77,6 @@ public function preProcess() { if ($cid) { CRM_Contact_Form_Task_PDFLetterCommon::preProcessSingle($this, $cid); $this->_single = TRUE; - $this->_cid = $cid; } else { parent::preProcess(); @@ -85,9 +88,9 @@ public function preProcess() { * Set default values for the form. */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (isset($this->_activityId)) { - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; CRM_Activity_BAO_Activity::retrieve($params, $defaults); $defaults['html_message'] = CRM_Utils_Array::value('details', $defaults); } @@ -118,8 +121,9 @@ public function postProcess() { */ public function listTokens() { $tokens = CRM_Core_SelectValues::contactTokens(); - if (isset($this->_caseId)) { - $caseTypeId = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $this->_caseId, 'case_type_id'); + if (isset($this->_caseId) || isset($this->_caseIds)) { + // For a single case, list tokens relevant for only that case type + $caseTypeId = isset($this->_caseId) ? CRM_Core_DAO::getFieldValue('CRM_Case_DAO_Case', $this->_caseId, 'case_type_id') : NULL; $tokens += CRM_Core_SelectValues::caseTokens($caseTypeId); } return $tokens; diff --git a/CRM/Contact/Form/Task/PDFLetterCommon.php b/CRM/Contact/Form/Task/PDFLetterCommon.php index 6f1003913f9d..a0424e1d6cf7 100644 --- a/CRM/Contact/Form/Task/PDFLetterCommon.php +++ b/CRM/Contact/Form/Task/PDFLetterCommon.php @@ -1,9 +1,9 @@ string $label). */ public static function getLoggingOptions() { - return array( + return [ 'none' => ts('Do not record'), 'multiple' => ts('Multiple activities (one per contact)'), 'combined' => ts('One combined activity'), 'combined-attached' => ts('One combined activity plus one file attachment'), // 'multiple-attached' <== not worth the work - ); + ]; } /** @@ -56,8 +58,9 @@ public static function getLoggingOptions() { * @param CRM_Core_Form $form */ public static function preProcess(&$form) { - $messageText = array(); - $messageSubject = array(); + CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($form); + $messageText = []; + $messageSubject = []; $dao = new CRM_Core_BAO_MessageTemplate(); $dao->is_active = 1; $dao->find(); @@ -68,7 +71,7 @@ public static function preProcess(&$form) { $form->assign('message', $messageText); $form->assign('messageSubject', $messageSubject); - CRM_Utils_System::setTitle('Print/Merge Document'); + parent::preProcess($form); } /** @@ -76,193 +79,11 @@ public static function preProcess(&$form) { * @param int $cid */ public static function preProcessSingle(&$form, $cid) { - $form->_contactIds = array($cid); + $form->_contactIds = explode(',', $cid); // put contact display name in title for single contact mode - CRM_Utils_System::setTitle(ts('Print/Merge Document for %1', array(1 => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $cid, 'display_name')))); - } - - /** - * Build the form object. - * - * @var CRM_Core_Form $form - */ - public static function buildQuickForm(&$form) { - // This form outputs a file so should never be submitted via ajax - $form->preventAjaxSubmit(); - - //Added for CRM-12682: Add activity subject and campaign fields - CRM_Campaign_BAO_Campaign::addCampaign($form); - $form->add( - 'text', - 'subject', - ts('Activity Subject'), - array('size' => 45, 'maxlength' => 255), - FALSE - ); - - $form->add('static', 'pdf_format_header', NULL, ts('Page Format: %1', array(1 => ''))); - $form->addSelect('format_id', array( - 'label' => ts('Select Format'), - 'placeholder' => ts('Default'), - 'entity' => 'message_template', - 'field' => 'pdf_format_id', - 'option_url' => 'civicrm/admin/pdfFormats', - )); - $form->add( - 'select', - 'paper_size', - ts('Paper Size'), - array(0 => ts('- default -')) + CRM_Core_BAO_PaperSize::getList(TRUE), - FALSE, - array('onChange' => "selectPaper( this.value ); showUpdateFormatChkBox();") - ); - $form->add('static', 'paper_dimensions', NULL, ts('Width x Height')); - $form->add( - 'select', - 'orientation', - ts('Orientation'), - CRM_Core_BAO_PdfFormat::getPageOrientations(), - FALSE, - array('onChange' => "updatePaperDimensions(); showUpdateFormatChkBox();") - ); - $form->add( - 'select', - 'metric', - ts('Unit of Measure'), - CRM_Core_BAO_PdfFormat::getUnits(), - FALSE, - array('onChange' => "selectMetric( this.value );") - ); - $form->add( - 'text', - 'margin_left', - ts('Left Margin'), - array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"), - TRUE - ); - $form->add( - 'text', - 'margin_right', - ts('Right Margin'), - array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"), - TRUE - ); - $form->add( - 'text', - 'margin_top', - ts('Top Margin'), - array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"), - TRUE - ); - $form->add( - 'text', - 'margin_bottom', - ts('Bottom Margin'), - array('size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"), - TRUE - ); - - $config = CRM_Core_Config::singleton(); - /** CRM-15883 Suppressing Stationery path field until we switch from DOMPDF to a library that supports it. - if ($config->wkhtmltopdfPath == FALSE) { - $form->add( - 'text', - 'stationery', - ts('Stationery (relative path to PDF you wish to use as the background)'), - array('size' => 25, 'maxlength' => 900, 'onkeyup' => "showUpdateFormatChkBox();"), - FALSE - ); + if (count($form->_contactIds) === 1) { + CRM_Utils_System::setTitle(ts('Print/Merge Document for %1', [1 => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $cid, 'display_name')])); } - */ - $form->add('checkbox', 'bind_format', ts('Always use this Page Format with the selected Template')); - $form->add('checkbox', 'update_format', ts('Update Page Format (this will affect all templates that use this format)')); - - $form->assign('useThisPageFormat', ts('Always use this Page Format with the new template?')); - $form->assign('useSelectedPageFormat', ts('Should the new template always use the selected Page Format?')); - $form->assign('totalSelectedContacts', count($form->_contactIds)); - - $form->add('select', 'document_type', ts('Document Type'), CRM_Core_SelectValues::documentFormat()); - - $documentTypes = implode(',', CRM_Core_SelectValues::documentApplicationType()); - $form->addElement('file', "document_file", 'Upload Document', 'size=30 maxlength=255 accept="' . $documentTypes . '"'); - $form->addUploadElement("document_file"); - - CRM_Mailing_BAO_Mailing::commonCompose($form); - - $buttons = array(); - if ($form->get('action') != CRM_Core_Action::VIEW) { - $buttons[] = array( - 'type' => 'upload', - 'name' => ts('Download Document'), - 'isDefault' => TRUE, - 'icon' => 'fa-download', - ); - $buttons[] = array( - 'type' => 'submit', - 'name' => ts('Preview'), - 'subName' => 'preview', - 'icon' => 'fa-search', - 'isDefault' => FALSE, - ); - } - $buttons[] = array( - 'type' => 'cancel', - 'name' => $form->get('action') == CRM_Core_Action::VIEW ? ts('Done') : ts('Cancel'), - ); - $form->addButtons($buttons); - - $form->addFormRule(array('CRM_Contact_Form_Task_PDFLetterCommon', 'formRule'), $form); - } - - /** - * Set default values. - */ - public static function setDefaultValues() { - $defaultFormat = CRM_Core_BAO_PdfFormat::getDefaultValues(); - $defaultFormat['format_id'] = $defaultFormat['id']; - return $defaultFormat; - } - - /** - * Form rule. - * - * @param array $fields - * The input form values. - * @param array $files - * @param array $self - * Additional values form 'this'. - * - * @return bool - * TRUE if no errors, else array of errors. - */ - public static function formRule($fields, $files, $self) { - $errors = array(); - $template = CRM_Core_Smarty::singleton(); - - // If user uploads non-document file other than odt/docx - if (empty($fields['template']) && - !empty($files['document_file']['tmp_name']) && - array_search($files['document_file']['type'], CRM_Core_SelectValues::documentApplicationType()) == NULL - ) { - $errors['document_file'] = ts('Invalid document file format'); - } - //Added for CRM-1393 - if (!empty($fields['saveTemplate']) && empty($fields['saveTemplateName'])) { - $errors['saveTemplateName'] = ts("Enter name to save message template"); - } - if (!is_numeric($fields['margin_left'])) { - $errors['margin_left'] = 'Margin must be numeric'; - } - if (!is_numeric($fields['margin_right'])) { - $errors['margin_right'] = 'Margin must be numeric'; - } - if (!is_numeric($fields['margin_top'])) { - $errors['margin_top'] = 'Margin must be numeric'; - } - if (!is_numeric($fields['margin_bottom'])) { - $errors['margin_bottom'] = 'Margin must be numeric'; - } - return empty($errors) ? TRUE : $errors; } /** @@ -275,62 +96,9 @@ public static function formRule($fields, $files, $self) { * [$categories, $html_message, $messageToken, $returnProperties] */ public static function processMessageTemplate($formValues) { - $html_message = CRM_Utils_Array::value('html_message', $formValues); - - // process message template - if (!empty($formValues['saveTemplate']) || !empty($formValues['updateTemplate'])) { - $messageTemplate = array( - 'msg_text' => NULL, - 'msg_html' => $formValues['html_message'], - 'msg_subject' => NULL, - 'is_active' => TRUE, - ); - - $messageTemplate['pdf_format_id'] = 'null'; - if (!empty($formValues['bind_format']) && $formValues['format_id']) { - $messageTemplate['pdf_format_id'] = $formValues['format_id']; - } - if (!empty($formValues['saveTemplate']) && $formValues['saveTemplate']) { - $messageTemplate['msg_title'] = $formValues['saveTemplateName']; - CRM_Core_BAO_MessageTemplate::add($messageTemplate); - } - - if (!empty($formValues['updateTemplate']) && $formValues['template'] && $formValues['updateTemplate']) { - $messageTemplate['id'] = $formValues['template']; + $html_message = self::processTemplate($formValues); - unset($messageTemplate['msg_title']); - CRM_Core_BAO_MessageTemplate::add($messageTemplate); - } - } - elseif (CRM_Utils_Array::value('template', $formValues) > 0) { - if (!empty($formValues['bind_format']) && $formValues['format_id']) { - $query = "UPDATE civicrm_msg_template SET pdf_format_id = {$formValues['format_id']} WHERE id = {$formValues['template']}"; - } - else { - $query = "UPDATE civicrm_msg_template SET pdf_format_id = NULL WHERE id = {$formValues['template']}"; - } - CRM_Core_DAO::executeQuery($query); - - $documentInfo = CRM_Core_BAO_File::getEntityFile('civicrm_msg_template', $formValues['template']); - foreach ((array) $documentInfo as $info) { - list($html_message, $formValues['document_type']) = CRM_Utils_PDF_Document::docReader($info['fullPath'], $info['mime_type']); - $formValues['document_file_path'] = $info['fullPath']; - } - } - // extract the content of uploaded document file - elseif (!empty($formValues['document_file'])) { - list($html_message, $formValues['document_type']) = CRM_Utils_PDF_Document::docReader($formValues['document_file']['name'], $formValues['document_file']['type']); - $formValues['document_file_path'] = $formValues['document_file']['name']; - } - - if (!empty($formValues['update_format'])) { - $bao = new CRM_Core_BAO_PdfFormat(); - $bao->savePdfFormat($formValues, $formValues['format_id']); - } - - $tokens = array(); - CRM_Utils_Hook::tokens($tokens); - $categories = array_keys($tokens); + $categories = self::getTokenCategories(); //time being hack to strip ' ' //from particular letter line, CRM-6798 @@ -338,32 +106,33 @@ public static function processMessageTemplate($formValues) { $messageToken = CRM_Utils_Token::getTokens($html_message); - $returnProperties = array(); + $returnProperties = []; if (isset($messageToken['contact'])) { foreach ($messageToken['contact'] as $key => $value) { $returnProperties[$value] = 1; } } - return array($formValues, $categories, $html_message, $messageToken, $returnProperties); + return [$formValues, $categories, $html_message, $messageToken, $returnProperties]; } /** * Process the form after the input has been submitted and validated. * * @param CRM_Core_Form $form + * + * @throws \CRM_Core_Exception */ public static function postProcess(&$form) { $formValues = $form->controller->exportValues($form->getName()); list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($formValues); - $buttonName = $form->controller->getButtonName(); $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE; $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE; - $html = $activityIds = array(); + $html = $activityIds = []; // CRM-16725 Skip creation of activities if user is previewing their PDF letter(s) - if ($buttonName == '_qf_PDF_upload') { - $activityIds = self::createActivities($form, $html_message, $form->_contactIds); + if (self::isLiveMode($form)) { + $activityIds = self::createActivities($form, $html_message, $form->_contactIds, $formValues['subject'], CRM_Utils_Array::value('campaign_id', $formValues)); } if (!empty($formValues['document_file_path'])) { @@ -372,7 +141,7 @@ public static function postProcess(&$form) { foreach ($form->_contactIds as $item => $contactId) { $caseId = NULL; - $params = array('contact_id' => $contactId); + $params = ['contact_id' => $contactId]; list($contact) = CRM_Utils_Token::getTokenDetails($params, $returnProperties, @@ -411,7 +180,7 @@ public static function postProcess(&$form) { } $tee = NULL; - if (Civi::settings()->get('recordGeneratedLetters') === 'combined-attached') { + if (self::isLiveMode($form) && Civi::settings()->get('recordGeneratedLetters') === 'combined-attached') { if (count($activityIds) !== 1) { throw new CRM_Core_Exception("When recordGeneratedLetters=combined-attached, there should only be one activity."); } @@ -442,27 +211,31 @@ public static function postProcess(&$form) { throw new \CRM_Core_Exception("Failed to capture document content (type=$type)!"); } foreach ($activityIds as $activityId) { - civicrm_api3('Attachment', 'create', array( + civicrm_api3('Attachment', 'create', [ 'entity_table' => 'civicrm_activity', 'entity_id' => $activityId, 'name' => $fileName, 'mime_type' => $mimeType, - 'options' => array( + 'options' => [ 'move-file' => $tee->getFileName(), - ), - )); + ], + ]); } } $form->postProcessHook(); - CRM_Utils_System::civiExit(1); + CRM_Utils_System::civiExit(); } /** * @param CRM_Core_Form $form * @param string $html_message * @param array $contactIds + * @param string $subject + * @param int $campaign_id + * @param array $perContactHtml + * * @return array * List of activity IDs. * There may be 1 or more, depending on the system-settings @@ -470,47 +243,43 @@ public static function postProcess(&$form) { * * @throws CRM_Core_Exception */ - public static function createActivities($form, $html_message, $contactIds) { - //Added for CRM-12682: Add activity subject and campaign fields - $formValues = $form->controller->exportValues($form->getName()); + public static function createActivities($form, $html_message, $contactIds, $subject, $campaign_id, $perContactHtml = []) { - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); - $activityTypeID = CRM_Core_OptionGroup::getValue( - 'activity_type', - 'Print PDF Letter', - 'name' - ); - $activityParams = array( - 'subject' => $formValues['subject'], - 'campaign_id' => CRM_Utils_Array::value('campaign_id', $formValues), - 'source_contact_id' => $userID, - 'activity_type_id' => $activityTypeID, + $activityParams = [ + 'subject' => $subject, + 'campaign_id' => $campaign_id, + 'source_contact_id' => CRM_Core_Session::singleton()->getLoggedInContactID(), + 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Print PDF Letter'), 'activity_date_time' => date('YmdHis'), 'details' => $html_message, - ); + ]; if (!empty($form->_activityId)) { - $activityParams += array('id' => $form->_activityId); + $activityParams += ['id' => $form->_activityId]; } - // This seems silly, but the old behavior was to first check `_cid` - // and then use the provided `$contactIds`. Probably not even necessary, - // but difficult to audit. - $contactIds = $form->_cid ? array($form->_cid) : $contactIds; - - $activityIds = array(); + $activityIds = []; switch (Civi::settings()->get('recordGeneratedLetters')) { case 'none': - return array(); + return []; case 'multiple': // One activity per contact. - foreach ($contactIds as $contactId) { - $fullParams = array( + foreach ($contactIds as $i => $contactId) { + $fullParams = [ 'target_contact_id' => $contactId, - ) + $activityParams; - $activity = CRM_Activity_BAO_Activity::create($fullParams); - $activityIds[$contactId] = $activity->id; + ] + $activityParams; + if (!empty($form->_caseId)) { + $fullParams['case_id'] = $form->_caseId; + } + elseif (!empty($form->_caseIds[$i])) { + $fullParams['case_id'] = $form->_caseIds[$i]; + } + + if (isset($perContactHtml[$contactId])) { + $fullParams['details'] = implode('
    ', $perContactHtml[$contactId]); + } + $activity = civicrm_api3('Activity', 'create', $fullParams); + $activityIds[$contactId] = $activity['id']; } break; @@ -518,70 +287,26 @@ public static function createActivities($form, $html_message, $contactIds) { case 'combined': case 'combined-attached': // One activity with all contacts. - $fullParams = array( + $fullParams = [ 'target_contact_id' => $contactIds, - ) + $activityParams; - $activity = CRM_Activity_BAO_Activity::create($fullParams); - $activityIds[] = $activity->id; + ] + $activityParams; + if (!empty($form->_caseId)) { + $fullParams['case_id'] = $form->_caseId; + } + elseif (!empty($form->_caseIds)) { + $fullParams['case_id'] = $form->_caseIds; + } + $activity = civicrm_api3('Activity', 'create', $fullParams); + $activityIds[] = $activity['id']; break; default: throw new CRM_Core_Exception("Unrecognized option in recordGeneratedLetters: " . Civi::settings()->get('recordGeneratedLetters')); } - if (!empty($form->_caseId)) { - foreach ($activityIds as $activityId) { - $caseActivityParams = array('activity_id' => $activityId, 'case_id' => $form->_caseId); - CRM_Case_BAO_Case::processCaseActivity($caseActivityParams); - } - } - return $activityIds; } - /** - * @param $message - */ - public static function formatMessage(&$message) { - $newLineOperators = array( - 'p' => array( - 'oper' => '

    ', - 'pattern' => '/<(\s+)?p(\s+)?>/m', - ), - 'br' => array( - 'oper' => '
    ', - 'pattern' => '/<(\s+)?br(\s+)?\/>/m', - ), - ); - - $htmlMsg = preg_split($newLineOperators['p']['pattern'], $message); - foreach ($htmlMsg as $k => & $m) { - $messages = preg_split($newLineOperators['br']['pattern'], $m); - foreach ($messages as $key => & $msg) { - $msg = trim($msg); - $matches = array(); - if (preg_match('/^( )+/', $msg, $matches)) { - $spaceLen = strlen($matches[0]) / 6; - $trimMsg = ltrim($msg, '  '); - $charLen = strlen($trimMsg); - $totalLen = $charLen + $spaceLen; - if ($totalLen > 100) { - $spacesCount = 10; - if ($spaceLen > 50) { - $spacesCount = 20; - } - if ($charLen > 100) { - $spacesCount = 1; - } - $msg = str_repeat(' ', $spacesCount) . $trimMsg; - } - } - } - $m = implode($newLineOperators['br']['oper'], $messages); - } - $message = implode($newLineOperators['p']['oper'], $htmlMsg); - } - /** * Convert from a vague-type/file-extension to mime-type. * @@ -590,12 +315,12 @@ public static function formatMessage(&$message) { * @throws \CRM_Core_Exception */ private static function getMimeType($type) { - $mimeTypes = array( + $mimeTypes = [ 'pdf' => 'application/pdf', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'odt' => 'application/vnd.oasis.opendocument.text', 'html' => 'text/html', - ); + ]; if (isset($mimeTypes[$type])) { return $mimeTypes[$type]; } @@ -604,4 +329,39 @@ private static function getMimeType($type) { } } + /** + * Get the categories required for rendering tokens. + * + * @return array + */ + protected static function getTokenCategories() { + if (!isset(Civi::$statics[__CLASS__]['token_categories'])) { + $tokens = []; + CRM_Utils_Hook::tokens($tokens); + Civi::$statics[__CLASS__]['token_categories'] = array_keys($tokens); + } + return Civi::$statics[__CLASS__]['token_categories']; + } + + /** + * Is the form in live mode (as opposed to being run as a preview). + * + * Returns true if the user has clicked the Download Document button on a + * Print/Merge Document (PDF Letter) search task form, or false if the Preview + * button was clicked. + * + * @param CRM_Core_Form $form + * + * @return bool + * TRUE if the Download Document button was clicked (also defaults to TRUE + * if the form controller does not exist), else FALSE + */ + protected static function isLiveMode($form) { + // CRM-21255 - Hrm, CiviCase 4+5 seem to report buttons differently... + $buttonName = $form->controller->getButtonName(); + $c = $form->controller->container(); + $isLiveMode = ($buttonName == '_qf_PDF_upload') || isset($c['values']['PDF']['buttons']['_qf_PDF_upload']); + return $isLiveMode; + } + } diff --git a/CRM/Contact/Form/Task/PickProfile.php b/CRM/Contact/Form/Task/PickProfile.php index 46a5d67a6c99..a3a061b8dde3 100644 --- a/CRM/Contact/Form/Task/PickProfile.php +++ b/CRM/Contact/Form/Task/PickProfile.php @@ -1,9 +1,9 @@ _contactIds) > $this->_maxContacts) { - CRM_Core_Session::setStatus(ts("The maximum number of contacts you can select for Update multiple contacts is %1. You have selected %2. Please select fewer contacts from your search results and try again.", array( - 1 => $this->_maxContacts, - 2 => count($this->_contactIds), - )), ts('Maximum Exceeded'), 'error'); + CRM_Core_Session::setStatus(ts("The maximum number of contacts you can select for Update multiple contacts is %1. You have selected %2. Please select fewer contacts from your search results and try again.", [ + 1 => $this->_maxContacts, + 2 => count($this->_contactIds), + ]), ts('Maximum Exceeded'), 'error'); $validate = TRUE; } @@ -106,10 +109,10 @@ public function buildQuickForm() { if (empty($profiles)) { $types = implode(' ' . ts('or') . ' ', $this->_contactTypes); - CRM_Core_Session::setStatus(ts("The contact type selected for Update multiple contacts does not have a corresponding profile. Please set up a profile for %1s and try again.", array(1 => $types)), ts('No Profile Available'), 'error'); + CRM_Core_Session::setStatus(ts("The contact type selected for Update multiple contacts does not have a corresponding profile. Please set up a profile for %1s and try again.", [1 => $types]), ts('No Profile Available'), 'error'); CRM_Utils_System::redirect($this->_userContext); } - $ufGroupElement = $this->add('select', 'uf_group_id', ts('Select Profile'), array('' => ts('- select profile -')) + $profiles, TRUE, array('class' => 'crm-select2 huge')); + $ufGroupElement = $this->add('select', 'uf_group_id', ts('Select Profile'), ['' => ts('- select profile -')] + $profiles, TRUE, ['class' => 'crm-select2 huge']); $this->addDefaultButtons(ts('Continue')); } @@ -118,7 +121,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contact_Form_Task_PickProfile', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_PickProfile', 'formRule']); } /** diff --git a/CRM/Contact/Form/Task/Print.php b/CRM/Contact/Form/Task/Print.php index 4087d2bcb10a..911af3dd8257 100644 --- a/CRM/Contact/Form/Task/Print.php +++ b/CRM/Contact/Form/Task/Print.php @@ -1,9 +1,9 @@ _contactIds as $contactId) { - $params[] = array( + $params[] = [ CRM_Core_Form::CB_PREFIX . $contactId, '=', 1, 0, 0, - ); + ]; } } @@ -82,8 +82,9 @@ public function preProcess() { $selectorName = $this->controller->selectorName(); require_once str_replace('_', DIRECTORY_SEPARATOR, $selectorName) . '.php'; - $returnP = isset($returnPropeties) ? $returnPropeties : ""; + $returnP = isset($returnProperties) ? $returnProperties : ""; $customSearchClass = $this->get('customSearchClass'); + $this->assign('customSearchID', $this->get('customSearchID')); $selector = new $selectorName($customSearchClass, $fv, $params, @@ -111,19 +112,18 @@ public function buildQuickForm() { // // just need to add a javacript to popup the window for printing // - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Print Contact List'), - 'js' => array('onclick' => 'window.print()'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Done'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Print Contact List'), + 'js' => ['onclick' => 'window.print()'], + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Done'), + ], + ]); } /** diff --git a/CRM/Contact/Form/Task/ProximityCommon.php b/CRM/Contact/Form/Task/ProximityCommon.php index f1ab27e3d041..b4a0bb0ab3bf 100644 --- a/CRM/Contact/Form/Task/ProximityCommon.php +++ b/CRM/Contact/Form/Task/ProximityCommon.php @@ -1,9 +1,9 @@ assign('proximity_search', TRUE); @@ -74,18 +74,18 @@ static public function buildQuickForm($form, $proxSearch) { $form->add('text', 'prox_postal_code', ts('Postal Code'), NULL, FALSE); - $form->addChainSelect('prox_state_province_id', array('required' => $proxRequired)); + $form->addChainSelect('prox_state_province_id', ['required' => $proxRequired]); - $country = array('' => ts('- select -')) + CRM_Core_PseudoConstant::country(); + $country = ['' => ts('- select -')] + CRM_Core_PseudoConstant::country(); $form->add('select', 'prox_country_id', ts('Country'), $country, $proxRequired); $form->add('text', 'prox_distance', ts('Distance'), NULL, $proxRequired); - $proxUnits = array('km' => ts('km'), 'miles' => ts('miles')); + $proxUnits = ['km' => ts('km'), 'miles' => ts('miles')]; $form->add('select', 'prox_distance_unit', ts('Units'), $proxUnits, $proxRequired); // prox_distance_unit - $form->addFormRule(array('CRM_Contact_Form_Task_ProximityCommon', 'formRule'), $form); + $form->addFormRule(['CRM_Contact_Form_Task_ProximityCommon', 'formRule'], $form); } /** @@ -101,15 +101,13 @@ static public function buildQuickForm($form, $proxSearch) { * true if no errors, else array of errors */ public static function formRule($fields, $files, $form) { - $errors = array(); + $errors = []; // If Distance is present, make sure state, country and city or postal code are populated. if (!empty($fields['prox_distance'])) { if (empty($fields['prox_state_province_id']) || empty($fields['prox_country_id'])) { $errors["prox_state_province_id"] = ts("Country AND State/Province are required to search by distance."); } - if (!CRM_Utils_Array::value('prox_postal_code', $fields) AND - !CRM_Utils_Array::value('prox_city', $fields) - ) { + if (empty($fields['prox_postal_code']) && empty($fields['prox_city'])) { $errors["prox_distance"] = ts("City OR Postal Code are required to search by distance."); } } @@ -125,8 +123,8 @@ public static function formRule($fields, $files, $form) { * @return array * the default array reference */ - static public function setDefaultValues($form) { - $defaults = array(); + public static function setDefaultValues($form) { + $defaults = []; $config = CRM_Core_Config::singleton(); $countryDefault = $config->defaultContactCountry; diff --git a/CRM/Contact/Form/Task/RemoveFromGroup.php b/CRM/Contact/Form/Task/RemoveFromGroup.php index 2a7988082b8f..d803f436e7b6 100644 --- a/CRM/Contact/Form/Task/RemoveFromGroup.php +++ b/CRM/Contact/Form/Task/RemoveFromGroup.php @@ -1,9 +1,9 @@ ts('- select group -')) + CRM_Core_PseudoConstant::nestedGroup(); - $groupElement = $this->add('select', 'group_id', ts('Select Group'), $group, TRUE, array('class' => 'crm-select2 huge')); + $group = ['' => ts('- select group -')] + CRM_Core_PseudoConstant::nestedGroup(); + $groupElement = $this->add('select', 'group_id', ts('Select Group'), $group, TRUE, ['class' => 'crm-select2 huge']); CRM_Utils_System::setTitle(ts('Remove Contacts from Group')); $this->addDefaultButtons(ts('Remove from Group')); @@ -58,7 +58,7 @@ public function buildQuickForm() { * the default array reference */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if ($this->get('context') === 'smog') { $defaults['group_id'] = $this->get('gid'); @@ -75,24 +75,24 @@ public function postProcess() { list($total, $removed, $notRemoved) = CRM_Contact_BAO_GroupContact::removeContactsFromGroup($this->_contactIds, $groupId); - $status = array( - ts("%count contact removed from '%2'", array( + $status = [ + ts("%count contact removed from '%2'", [ 'count' => $removed, 'plural' => "%count contacts removed from '%2'", 2 => $group[$groupId], - )), - ); + ]), + ]; if ($notRemoved) { - $status[] = ts('1 contact was already not in this group', array( - 'count' => $notRemoved, - 'plural' => '%count contacts were already not in this group', - )); + $status[] = ts('1 contact was already not in this group', [ + 'count' => $notRemoved, + 'plural' => '%count contacts were already not in this group', + ]); } $status = '

    • ' . implode('
    • ', $status) . '
    '; - CRM_Core_Session::setStatus($status, ts("Removed Contact From Group", array( - 'plural' => "Removed Contacts From Group", - 'count' => $removed, - )), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts("Removed Contact From Group", [ + 'plural' => "Removed Contacts From Group", + 'count' => $removed, + ]), 'success', ['expires' => 0]); } } diff --git a/CRM/Contact/Form/Task/RemoveFromTag.php b/CRM/Contact/Form/Task/RemoveFromTag.php index f6847cacd41d..3c18e5b3e2e3 100644 --- a/CRM/Contact/Form/Task/RemoveFromTag.php +++ b/CRM/Contact/Form/Task/RemoveFromTag.php @@ -1,9 +1,9 @@ addFormRule(array('CRM_Contact_Form_Task_RemoveFromTag', 'formRule')); + $this->addFormRule(['CRM_Contact_Form_Task_RemoveFromTag', 'formRule']); } /** @@ -77,7 +77,7 @@ public function addRules() { * @return array */ public static function formRule($form, $rule) { - $errors = array(); + $errors = []; if (empty($form['tag']) && empty($form['contact_taglist'])) { $errors['_qf_default'] = "Please select atleast one tag."; } @@ -91,7 +91,7 @@ public function postProcess() { //get the submitted values in an array $params = $this->controller->exportValues($this->_name); - $contactTags = $tagList = array(); + $contactTags = $tagList = []; // check if contact tags exists if (!empty($params['tag'])) { @@ -120,27 +120,27 @@ public function postProcess() { // merge contact and taglist tags $allTags = CRM_Utils_Array::crmArrayMerge($contactTags, $tagList); - $this->_name = array(); + $this->_name = []; foreach ($allTags as $key => $dnc) { $this->_name[] = $this->_tags[$key]; list($total, $removed, $notRemoved) = CRM_Core_BAO_EntityTag::removeEntitiesFromTag($this->_contactIds, $key, 'civicrm_contact', FALSE); - $status = array( - ts('%count contact un-tagged', array( - 'count' => $removed, - 'plural' => '%count contacts un-tagged', - )), - ); + $status = [ + ts('%count contact un-tagged', [ + 'count' => $removed, + 'plural' => '%count contacts un-tagged', + ]), + ]; if ($notRemoved) { - $status[] = ts('1 contact already did not have this tag', array( - 'count' => $notRemoved, - 'plural' => '%count contacts already did not have this tag', - )); + $status[] = ts('1 contact already did not have this tag', [ + 'count' => $notRemoved, + 'plural' => '%count contacts already did not have this tag', + ]); } $status = '
    • ' . implode('
    • ', $status) . '
    '; - CRM_Core_Session::setStatus($status, ts("Removed Tag %1", array(1 => $this->_tags[$key])), 'success', array('expires' => 0)); + CRM_Core_Session::setStatus($status, ts("Removed Tag %1", [1 => $this->_tags[$key]]), 'success', ['expires' => 0]); } } diff --git a/CRM/Contact/Form/Task/Result.php b/CRM/Contact/Form/Task/Result.php index 1f6f8e678a94..22c8bea27382 100644 --- a/CRM/Contact/Form/Task/Result.php +++ b/CRM/Contact/Form/Task/Result.php @@ -1,9 +1,9 @@ set('searchRows', ''); $context = $this->get('context'); - if (in_array($context, array( + if (in_array($context, [ 'smog', 'amtg', - ))) { + ])) { $urlParams = 'reset=1&force=1&context=smog&gid='; $urlParams .= ($context == 'smog') ? $this->get('gid') : $this->get('amtgID'); $session->replaceUserContext(CRM_Utils_System::url('civicrm/group/search', $urlParams)); @@ -94,14 +94,13 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Contact/Form/Task/SMS.php b/CRM/Contact/Form/Task/SMS.php index 0515118b0aed..b6a13f6cbc89 100644 --- a/CRM/Contact/Form/Task/SMS.php +++ b/CRM/Contact/Form/Task/SMS.php @@ -1,9 +1,9 @@ _context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE); diff --git a/CRM/Contact/Form/Task/SMSCommon.php b/CRM/Contact/Form/Task/SMSCommon.php index a4ab022d269c..eba06f8006ed 100644 --- a/CRM/Contact/Form/Task/SMSCommon.php +++ b/CRM/Contact/Form/Task/SMSCommon.php @@ -1,9 +1,9 @@ _activityHolderIds)) { CRM_Core_Error::statusBounce(ts("The Reply SMS Could only be sent for activities with '%1' subject.", - array(1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT) + [1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT] )); } } @@ -88,11 +87,11 @@ public static function preProcessProvider(&$form) { */ public static function buildQuickForm(&$form) { - $toArray = array(); + $toArray = []; $providers = CRM_SMS_BAO_Provider::getProviders(NULL, NULL, TRUE, 'is_default desc'); - $providerSelect = array(); + $providerSelect = []; foreach ($providers as $provider) { $providerSelect[$provider['id']] = $provider['title']; } @@ -101,11 +100,11 @@ public static function buildQuickForm(&$form) { $cid = $form->get('cid'); if ($cid) { - $form->_contactIds = array($cid); + $form->_contactIds = [$cid]; } - $to = $form->add('text', 'to', ts('To'), array('class' => 'huge'), TRUE); - $form->add('text', 'activity_subject', ts('Name The SMS'), array('class' => 'huge'), TRUE); + $to = $form->add('text', 'to', ts('To'), ['class' => 'huge'], TRUE); + $form->add('text', 'activity_subject', ts('Name The SMS'), ['class' => 'huge'], TRUE); $toSetDefault = TRUE; if (property_exists($form, '_context') && $form->_context == 'standalone') { @@ -113,11 +112,11 @@ public static function buildQuickForm(&$form) { } // when form is submitted recompute contactIds - $allToSMS = array(); + $allToSMS = []; if ($to->getValue()) { $allToPhone = explode(',', $to->getValue()); - $form->_contactIds = array(); + $form->_contactIds = []; foreach ($allToPhone as $value) { list($contactId, $phone) = explode('::', $value); if ($contactId) { @@ -140,7 +139,7 @@ public static function buildQuickForm(&$form) { continue; } - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts); //target contacts limit check $ids = array_keys(CRM_Activity_BAO_ActivityContact::getNames($id, $targetID)); @@ -156,30 +155,30 @@ public static function buildQuickForm(&$form) { if (!$validActivities) { $errorMess = ""; if ($extendTargetContacts) { - $errorMess = ts('One selected activity consists of more than one target contact.', array( + $errorMess = ts('One selected activity consists of more than one target contact.', [ 'count' => $extendTargetContacts, 'plural' => '%count selected activities consist of more than one target contact.', - )); + ]); } if ($invalidActivity) { $errorMess = ($errorMess ? ' ' : ''); - $errorMess .= ts('The selected activity is invalid.', array( + $errorMess .= ts('The selected activity is invalid.', [ 'count' => $invalidActivity, 'plural' => '%count selected activities are invalid.', - )); + ]); } - CRM_Core_Error::statusBounce(ts("%1: SMS Reply will not be sent.", array(1 => $errorMess))); + CRM_Core_Error::statusBounce(ts("%1: SMS Reply will not be sent.", [1 => $errorMess])); } } if (is_array($form->_contactIds) && !empty($form->_contactIds) && $toSetDefault) { - $returnProperties = array( + $returnProperties = [ 'sort_name' => 1, 'phone' => 1, 'do_not_sms' => 1, 'is_deceased' => 1, 'display_name' => 1, - ); + ]; list($form->_contactDetails) = CRM_Utils_Token::getTokenDetails($form->_contactIds, $returnProperties, @@ -215,7 +214,7 @@ public static function buildQuickForm(&$form) { if (!empty($value['phone']) && $value['phone_type_id'] != CRM_Utils_Array::value('Mobile', $phoneTypes) && empty($value['is_deceased']) ) { - $filter = array('do_not_sms' => 0); + $filter = ['do_not_sms' => 0]; $contactPhones = CRM_Core_BAO_Phone::allPhones($contactId, FALSE, 'Mobile', $filter); if (count($contactPhones) > 0) { $mobilePhone = CRM_Utils_Array::retrieveValueRecursive($contactPhones, 'phone'); @@ -247,10 +246,10 @@ public static function buildQuickForm(&$form) { } if ($phone) { - $toArray[] = array( + $toArray[] = [ 'text' => '"' . $value['sort_name'] . '" (' . $phone . ')', 'id' => "$contactId::{$phone}", - ); + ]; } } @@ -294,7 +293,7 @@ public static function buildQuickForm(&$form) { $form->addDefaultButtons(ts('Send SMS'), 'upload'); } - $form->addFormRule(array('CRM_Contact_Form_Task_SMSCommon', 'formRule'), $form); + $form->addFormRule(['CRM_Contact_Form_Task_SMSCommon', 'formRule'], $form); } /** @@ -310,7 +309,7 @@ public static function buildQuickForm(&$form) { * true if no errors, else array of errors */ public static function formRule($fields, $dontCare, $self) { - $errors = array(); + $errors = []; $template = CRM_Core_Smarty::singleton(); @@ -322,7 +321,7 @@ public static function formRule($fields, $dontCare, $self) { $messageCheck = CRM_Utils_Array::value('sms_text_message', $fields); $messageCheck = str_replace("\r\n", "\n", $messageCheck); if ($messageCheck && (strlen($messageCheck) > CRM_SMS_Provider::MAX_SMS_CHAR)) { - $errors['sms_text_message'] = ts("You can configure the SMS message body up to %1 characters", array(1 => CRM_SMS_Provider::MAX_SMS_CHAR)); + $errors['sms_text_message'] = ts("You can configure the SMS message body up to %1 characters", [1 => CRM_SMS_Provider::MAX_SMS_CHAR]); } } } @@ -349,11 +348,11 @@ public static function postProcess(&$form) { // process message template if (!empty($thisValues['SMSsaveTemplate']) || !empty($thisValues['SMSupdateTemplate'])) { - $messageTemplate = array( + $messageTemplate = [ 'msg_text' => $thisValues['sms_text_message'], 'is_active' => TRUE, 'is_sms' => TRUE, - ); + ]; if (!empty($thisValues['SMSsaveTemplate'])) { $messageTemplate['msg_title'] = $thisValues['SMSsaveTemplateName']; @@ -368,8 +367,8 @@ public static function postProcess(&$form) { } // format contact details array to handle multiple sms from same contact - $formattedContactDetails = array(); - $tempPhones = array(); + $formattedContactDetails = []; + $tempPhones = []; foreach ($form->_contactIds as $key => $contactId) { $phone = $form->_toContactPhone[$key]; @@ -400,10 +399,10 @@ public static function postProcess(&$form) { ); if ($countSuccess > 0) { - CRM_Core_Session::setStatus(ts('One message was sent successfully.', array( - 'plural' => '%count messages were sent successfully.', - 'count' => $countSuccess, - )), ts('Message Sent', array('plural' => 'Messages Sent', 'count' => $countSuccess)), 'success'); + CRM_Core_Session::setStatus(ts('One message was sent successfully.', [ + 'plural' => '%count messages were sent successfully.', + 'count' => $countSuccess, + ]), ts('Message Sent', ['plural' => 'Messages Sent', 'count' => $countSuccess]), 'success'); } if (is_array($sent)) { @@ -414,17 +413,17 @@ public static function postProcess(&$form) { $status .= '
  • ' . $errMsg . '
  • '; } $status .= ''; - CRM_Core_Session::setStatus($status, ts('One Message Not Sent', array( - 'count' => count($sent), - 'plural' => '%count Messages Not Sent', - )), 'info'); + CRM_Core_Session::setStatus($status, ts('One Message Not Sent', [ + 'count' => count($sent), + 'plural' => '%count Messages Not Sent', + ]), 'info'); } else { //Display the name and number of contacts for those sms is not sent. $smsNotSent = array_diff_assoc($allContactIds, $contactIds); if (!empty($smsNotSent)) { - $not_sent = array(); + $not_sent = []; foreach ($smsNotSent as $index => $contactId) { $displayName = $form->_allContactDetails[$contactId]['display_name']; $phone = $form->_allContactDetails[$contactId]['phone']; @@ -433,13 +432,13 @@ public static function postProcess(&$form) { } $status = '(' . ts('because no phone number on file or communication preferences specify DO NOT SMS or Contact is deceased'); if (CRM_Utils_System::getClassName($form) == 'CRM_Activity_Form_Task_SMS') { - $status .= ' ' . ts("or the contact is not part of the activity '%1'", array(1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT)); + $status .= ' ' . ts("or the contact is not part of the activity '%1'", [1 => self::RECIEVED_SMS_ACTIVITY_SUBJECT]); } $status .= ')'; - CRM_Core_Session::setStatus($status, ts('One Message Not Sent', array( - 'count' => count($smsNotSent), - 'plural' => '%count Messages Not Sent', - )), 'info'); + CRM_Core_Session::setStatus($status, ts('One Message Not Sent', [ + 'count' => count($smsNotSent), + 'plural' => '%count Messages Not Sent', + ]), 'info'); } } } diff --git a/CRM/Contact/Form/Task/SaveSearch.php b/CRM/Contact/Form/Task/SaveSearch.php index bff3f0d1291d..1b888a6b53f8 100644 --- a/CRM/Contact/Form/Task/SaveSearch.php +++ b/CRM/Contact/Form/Task/SaveSearch.php @@ -1,9 +1,9 @@ controller->exportValues('Basic'); } + // Get Task name + $modeValue = CRM_Contact_Form_Search::getModeValue(CRM_Utils_Array::value('component_mode', $values, CRM_Contact_BAO_Query::MODE_CONTACTS)); + $className = $modeValue['taskClassName']; + $taskList = $className::taskTitles(); $this->_task = CRM_Utils_Array::value('task', $values); - $crmContactTaskTasks = CRM_Contact_Task::taskTitles(); - $this->assign('taskName', CRM_Utils_Array::value($this->_task, $crmContactTaskTasks)); + $this->assign('taskName', CRM_Utils_Array::value($this->_task, $taskList)); } /** @@ -135,7 +138,7 @@ public function buildQuickForm() { $this->assign('partiallySelected', $formValues['radio_ts'] != 'ts_all'); } $this->addRule('title', ts('Name already exists in Database.'), - 'objectExists', array('CRM_Contact_DAO_Group', $groupID, 'title') + 'objectExists', ['CRM_Contact_DAO_Group', $groupID, 'title'] ); } @@ -157,12 +160,9 @@ public function postProcess() { if (!$this->_id) { //save record in mapping table - $mappingParams = array( - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Search Builder', - 'name' - ), - ); + $mappingParams = [ + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Search Builder'), + ]; $mapping = CRM_Core_BAO_Mapping::add($mappingParams); $mappingId = $mapping->id; } @@ -205,18 +205,15 @@ public function postProcess() { $savedSearch->search_custom_id = $this->get('customSearchID'); $savedSearch->save(); $this->set('ssID', $savedSearch->id); - CRM_Core_Session::setStatus(ts("Your smart group has been saved as '%1'.", array(1 => $formValues['title'])), ts('Group Saved'), 'success'); + CRM_Core_Session::setStatus(ts("Your smart group has been saved as '%1'.", [1 => $formValues['title']]), ts('Group Saved'), 'success'); // also create a group that is associated with this saved search only if new saved search - $params = array(); + $params = []; $params['title'] = $formValues['title']; $params['description'] = $formValues['description']; - if (isset($formValues['group_type']) && - is_array($formValues['group_type']) - ) { + if (isset($formValues['group_type']) && is_array($formValues['group_type']) && count($formValues['group_type'])) { $params['group_type'] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, - array_keys($formValues['group_type']) - ) . CRM_Core_DAO::VALUE_SEPARATOR; + array_keys($formValues['group_type'])) . CRM_Core_DAO::VALUE_SEPARATOR; } else { $params['group_type'] = ''; @@ -236,15 +233,12 @@ public function postProcess() { // Update mapping with the name and description of the group. if ($mappingId && $group) { - $mappingParams = array( + $mappingParams = [ 'id' => $mappingId, 'name' => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $group->id, 'name', 'id'), 'description' => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $group->id, 'description', 'id'), - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Search Builder', - 'name' - ), - ); + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Search Builder'), + ]; CRM_Core_BAO_Mapping::add($mappingParams); } @@ -263,7 +257,7 @@ public function postProcess() { * return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (empty($defaults['parents'])) { $defaults['parents'] = CRM_Core_BAO_Domain::getGroupId(); } diff --git a/CRM/Contact/Form/Task/SaveSearch/Update.php b/CRM/Contact/Form/Task/SaveSearch/Update.php index a4c42883a8c7..4d6e7446a719 100644 --- a/CRM/Contact/Form/Task/SaveSearch/Update.php +++ b/CRM/Contact/Form/Task/SaveSearch/Update.php @@ -1,9 +1,9 @@ $this->_id); + $params = ['saved_search_id' => $this->_id]; CRM_Contact_BAO_Group::retrieve($params, $defaults); return $defaults; diff --git a/CRM/Contact/Form/Task/Unhold.php b/CRM/Contact/Form/Task/Unhold.php index 3da999ed314b..add289176475 100644 --- a/CRM/Contact/Form/Task/Unhold.php +++ b/CRM/Contact/Form/Task/Unhold.php @@ -27,15 +27,15 @@ public function postProcess() { $rowCount = $result->affectedRows(); if ($rowCount) { - CRM_Core_Session::setStatus(ts('%count email was found on hold and updated.', array( - 'count' => $rowCount, - 'plural' => '%count emails were found on hold and updated.', - )), ts('Emails Restored'), 'success'); + CRM_Core_Session::setStatus(ts('%count email was found on hold and updated.', [ + 'count' => $rowCount, + 'plural' => '%count emails were found on hold and updated.', + ]), ts('Emails Restored'), 'success'); } else { - CRM_Core_Session::setStatus(ts('The selected contact does not have an email on hold.', array( - 'plural' => 'None of the selected contacts have an email on hold.', - )), ts('No Emails to Restore'), 'info'); + CRM_Core_Session::setStatus(ts('The selected contact does not have an email on hold.', [ + 'plural' => 'None of the selected contacts have an email on hold.', + ]), ts('No Emails to Restore'), 'info'); } } else { diff --git a/CRM/Contact/Form/Task/Useradd.php b/CRM/Contact/Form/Task/Useradd.php index abcf56f6d89c..303fee2ad5b6 100644 --- a/CRM/Contact/Form/Task/Useradd.php +++ b/CRM/Contact/Form/Task/Useradd.php @@ -1,9 +1,9 @@ _contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); $params['id'] = $params['contact_id'] = $this->_contactId; $contact = CRM_Contact_BAO_Contact::retrieve($params, $defaults, $ids); $this->_displayName = $contact->display_name; $this->_email = $contact->email; - CRM_Utils_System::setTitle(ts('Create User Record for %1', array(1 => $this->_displayName))); + CRM_Utils_System::setTitle(ts('Create User Record for %1', [1 => $this->_displayName])); } /** * Set default values for the form. */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; $defaults['contactID'] = $this->_contactId; $defaults['name'] = $this->_displayName; if (!empty($this->_email)) { @@ -80,32 +80,32 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { - $element = $this->add('text', 'name', ts('Full Name'), array('class' => 'huge')); + $element = $this->add('text', 'name', ts('Full Name'), ['class' => 'huge']); $element->freeze(); - $this->add('text', 'cms_name', ts('Username'), array('class' => 'huge')); + $this->add('text', 'cms_name', ts('Username'), ['class' => 'huge']); $this->addRule('cms_name', 'Username is required', 'required'); - $this->add('password', 'cms_pass', ts('Password'), array('class' => 'huge')); - $this->add('password', 'cms_confirm_pass', ts('Confirm Password'), array('class' => 'huge')); + $this->add('password', 'cms_pass', ts('Password'), ['class' => 'huge']); + $this->add('password', 'cms_confirm_pass', ts('Confirm Password'), ['class' => 'huge']); $this->addRule('cms_pass', 'Password is required', 'required'); - $this->addRule(array('cms_pass', 'cms_confirm_pass'), 'ERROR: Password mismatch', 'compare'); - $this->add('text', 'email', ts('Email:'), array('class' => 'huge'))->freeze(); + $this->addRule(['cms_pass', 'cms_confirm_pass'], 'ERROR: Password mismatch', 'compare'); + $this->add('text', 'email', ts('Email:'), ['class' => 'huge'])->freeze(); $this->add('hidden', 'contactID'); //add a rule to check username uniqueness - $this->addFormRule(array('CRM_Contact_Form_Task_Useradd', 'usernameRule')); + $this->addFormRule(['CRM_Contact_Form_Task_Useradd', 'usernameRule']); $this->addButtons( - array( - array( + [ + [ 'type' => 'next', 'name' => ts('Add'), 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'cancel', 'name' => ts('Cancel'), - ), - ) + ], + ] ); $this->setDefaults($this->setDefaultValues()); } @@ -130,11 +130,11 @@ public function postProcess() { */ public static function usernameRule($params) { $config = CRM_Core_Config::singleton(); - $errors = array(); - $check_params = array( + $errors = []; + $check_params = [ 'name' => $params['cms_name'], 'mail' => $params['email'], - ); + ]; $config->userSystem->checkUserNameEmailExists($check_params, $errors); return empty($errors) ? TRUE : $errors; diff --git a/CRM/Contact/Import/Controller.php b/CRM/Contact/Import/Controller.php index 63fdb0834679..b483639ce785 100644 --- a/CRM/Contact/Import/Controller.php +++ b/CRM/Contact/Import/Controller.php @@ -1,9 +1,9 @@ addActions($config->uploadDir, array('uploadFile')); + $this->addActions($config->uploadDir, ['uploadFile']); } } diff --git a/CRM/Contact/Import/Field.php b/CRM/Contact/Import/Field.php index 7e1a4b9423a6..9d6b2d8fa398 100644 --- a/CRM/Contact/Import/Field.php +++ b/CRM/Contact/Import/Field.php @@ -1,9 +1,9 @@ _lastError) { - CRM_Core_Error::fatal(ts('Database Configuration Error: Insufficient permissions. Import requires that the CiviCRM database user has permission to create temporary tables. Contact your site administrator for assistance.')); + $this->invalidConfig(ts('Database Configuration Error: Insufficient permissions. Import requires that the CiviCRM database user has permission to create temporary tables. Contact your site administrator for assistance.')); } - $results = array(); + $results = []; $config = CRM_Core_Config::singleton(); $handler = opendir($config->uploadDir); - $errorFiles = array('sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch'); + $errorFiles = ['sqlImport.errors', 'sqlImport.conflicts', 'sqlImport.duplicates', 'sqlImport.mismatch']; // check for post max size avoid when called twice $snippet = CRM_Utils_Array::value('snippet', $_GET, 0); @@ -81,19 +83,18 @@ public function preProcess() { } closedir($handler); if (!empty($results)) { - CRM_Core_Error::fatal(ts('%1 file(s) in %2 directory are not writable. Listed file(s) might be used during the import to log the errors occurred during Import process. Contact your site administrator for assistance.', array( - 1 => implode(', ', $results), - 2 => $config->uploadDir, - ))); + $this->invalidConfig(ts('%1 file(s) in %2 directory are not writable. Listed file(s) might be used during the import to log the errors occurred during Import process. Contact your site administrator for assistance.', [ + 1 => implode(', ', $results), + 2 => $config->uploadDir, + ])); } $this->_dataSourceIsValid = FALSE; - $this->_dataSource = CRM_Utils_Request::retrieve( + $this->_dataSource = CRM_Utils_Request::retrieveValue( 'dataSource', 'String', - CRM_Core_DAO::$_nullObject, - FALSE, NULL, + FALSE, 'GET' ); @@ -121,7 +122,7 @@ public function preProcess() { $this->assign('dataSourceFormTemplateFile', $templateFile); } elseif ($this->_dataSource) { - throw new \CRM_Core_Exception("Invalid data source"); + $this->invalidConfig('Invalid data source'); } } @@ -146,11 +147,11 @@ public function buildQuickForm() { $this->assign('urlPathVar', 'snippet=4'); $this->add('select', 'dataSource', ts('Data Source'), $dataSources, TRUE, - array('onchange' => 'buildDataSourceFormBlock(this.value);') + ['onchange' => 'buildDataSourceFormBlock(this.value);'] ); // duplicate handling options - $duplicateOptions = array(); + $duplicateOptions = []; $duplicateOptions[] = $this->createElement('radio', NULL, NULL, ts('Skip'), CRM_Import_Parser::DUPLICATE_SKIP ); @@ -168,17 +169,14 @@ public function buildQuickForm() { ts('For Duplicate Contacts') ); - $mappingArray = CRM_Core_BAO_Mapping::getMappings(CRM_Core_OptionGroup::getValue('mapping_type', - 'Import Contact', - 'name' - )); + $mappingArray = CRM_Core_BAO_Mapping::getMappings('Import Contact'); $this->assign('savedMapping', $mappingArray); - $this->addElement('select', 'savedMapping', ts('Mapping Option'), array('' => ts('- select -')) + $mappingArray); + $this->addElement('select', 'savedMapping', ts('Mapping Option'), ['' => ts('- select -')] + $mappingArray); - $js = array('onClick' => "buildSubTypes();buildDedupeRules();"); + $js = ['onClick' => "buildSubTypes();buildDedupeRules();"]; // contact types option - $contactOptions = array(); + $contactOptions = []; if (CRM_Contact_BAO_ContactType::isActive('Individual')) { $contactOptions[] = $this->createElement('radio', NULL, NULL, ts('Individual'), CRM_Import_Parser::CONTACT_INDIVIDUAL, $js @@ -206,31 +204,30 @@ public function buildQuickForm() { $config = CRM_Core_Config::singleton(); $geoCode = FALSE; - if (!empty($config->geocodeMethod)) { + if (CRM_Utils_GeocodeProvider::getUsableClassName()) { $geoCode = TRUE; $this->addElement('checkbox', 'doGeocodeAddress', ts('Geocode addresses during import?')); } $this->assign('geoCode', $geoCode); - $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), array('size' => 2)); + $this->addElement('text', 'fieldSeparator', ts('Import Field Separator'), ['size' => 2]); if (Civi::settings()->get('address_standardization_provider') == 'USPS') { $this->addElement('checkbox', 'disableUSPS', ts('Disable USPS address validation during import?')); } - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Continue'), - 'spacing' => '          ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Continue'), + 'spacing' => '          ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -243,12 +240,12 @@ public function buildQuickForm() { */ public function setDefaultValues() { $config = CRM_Core_Config::singleton(); - $defaults = array( + $defaults = [ 'dataSource' => 'CRM_Import_DataSource_CSV', 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP, 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL, 'fieldSeparator' => $config->fieldSeparator, - ); + ]; if ($loadeMapping = $this->get('loadedMapping')) { $this->assign('loadedMapping', $loadeMapping); @@ -271,17 +268,17 @@ private function _getDataSources() { // Open the data source dir and scan it for class files global $civicrm_root; $dataSourceDir = $civicrm_root . DIRECTORY_SEPARATOR . 'CRM' . DIRECTORY_SEPARATOR . 'Import' . DIRECTORY_SEPARATOR . 'DataSource' . DIRECTORY_SEPARATOR; - $dataSources = array(); + $dataSources = []; if (!is_dir($dataSourceDir)) { - CRM_Core_Error::fatal("Import DataSource directory $dataSourceDir does not exist"); + $this->invalidConfig("Import DataSource directory $dataSourceDir does not exist"); } if (!$dataSourceHandle = opendir($dataSourceDir)) { - CRM_Core_Error::fatal("Unable to access DataSource directory $dataSourceDir"); + $this->invalidConfig("Unable to access DataSource directory $dataSourceDir"); } while (($dataSourceFile = readdir($dataSourceHandle)) !== FALSE) { $fileType = filetype($dataSourceDir . $dataSourceFile); - $matches = array(); + $matches = []; if (($fileType == 'file' || $fileType == 'link') && preg_match('/^(.+)\.php$/', $dataSourceFile, $matches) ) { @@ -310,18 +307,17 @@ public function postProcess() { // Setup the params array $this->_params = $this->controller->exportValues($this->_name); - $storeParams = array( - 'onDuplicate' => 'onDuplicate', - 'dedupe' => 'dedupe', - 'contactType' => 'contactType', - 'contactSubType' => 'subType', - 'dateFormats' => 'dateFormats', - 'savedMapping' => 'savedMapping', - ); - - foreach ($storeParams as $storeName => $storeValueName) { - $$storeName = $this->exportValue($storeValueName); - $this->set($storeName, $$storeName); + $storeParams = [ + 'onDuplicate' => $this->exportValue('onDuplicate'), + 'dedupe' => $this->exportValue('dedupe'), + 'contactType' => $this->exportValue('contactType'), + 'contactSubType' => $this->exportValue('subType'), + 'dateFormats' => $this->exportValue('dateFormats'), + 'savedMapping' => $this->exportValue('savedMapping'), + ]; + + foreach ($storeParams as $storeName => $value) { + $this->set($storeName, $value); } $this->set('disableUSPS', !empty($this->_params['disableUSPS'])); @@ -329,7 +325,7 @@ public function postProcess() { $this->set('skipColumnHeader', CRM_Utils_Array::value('skipColumnHeader', $this->_params)); $session = CRM_Core_Session::singleton(); - $session->set('dateTypes', $dateFormats); + $session->set('dateTypes', $storeParams['dateFormats']); // Get the PEAR::DB object $dao = new CRM_Core_DAO(); @@ -346,28 +342,28 @@ public function postProcess() { // We should have the data in the DB now, parse it $importTableName = $this->get('importTableName'); $fieldNames = $this->_prepareImportTable($db, $importTableName); - $mapper = array(); + $mapper = []; $parser = new CRM_Contact_Import_Parser_Contact($mapper); $parser->setMaxLinesToProcess(100); $parser->run($importTableName, $mapper, CRM_Import_Parser::MODE_MAPFIELD, - $contactType, + $storeParams['contactType'], $fieldNames['pk'], $fieldNames['status'], CRM_Import_Parser::DUPLICATE_SKIP, NULL, NULL, FALSE, CRM_Contact_Import_Parser::DEFAULT_TIMEOUT, - $contactSubType, - $dedupe + $storeParams['contactSubType'], + $storeParams['dedupe'] ); // add all the necessary variables to the form $parser->set($this); } else { - CRM_Core_Error::fatal("Invalid DataSource on form post. This shouldn't happen!"); + $this->invalidConfig("Invalid DataSource on form post. This shouldn't happen!"); } } @@ -407,7 +403,22 @@ private function _prepareImportTable($db, $importTableName) { AUTO_INCREMENT"; $db->query($alterQuery); - return array('status' => $statusFieldName, 'pk' => $primaryKeyName); + return ['status' => $statusFieldName, 'pk' => $primaryKeyName]; + } + + /** + * General function for handling invalid configuration. + * + * I was going to statusBounce them all but when I tested I was 'bouncing' to weird places + * whereas throwing an exception gave no behaviour change. So, I decided to centralise + * and we can 'flip the switch' later. + * + * @param $message + * + * @throws \CRM_Core_Exception + */ + protected function invalidConfig($message) { + throw new CRM_Core_Exception($message); } /** diff --git a/CRM/Contact/Import/Form/MapField.php b/CRM/Contact/Import/Form/MapField.php index e6e67b34a6b8..0d2d3ce56ebe 100644 --- a/CRM/Contact/Import/Form/MapField.php +++ b/CRM/Contact/Import/Form/MapField.php @@ -1,9 +1,9 @@ _mapperFields)) { + if ($columnKey = array_search($columnName, $this->getFieldTitles())) { $this->_fieldUsed[$columnKey] = TRUE; return $columnKey; } } - foreach ($patterns as $key => $re) { + foreach ($this->getHeaderPatterns() as $key => $re) { // Skip empty key/patterns if (!$key || !$re || strlen("$re") < 5) { continue; @@ -98,7 +98,7 @@ public function preProcess() { $this->_mapperFields = $this->get('fields'); $this->_importTableName = $this->get('importTableName'); $this->_onDuplicate = $this->get('onDuplicate'); - $highlightedFields = array(); + $highlightedFields = []; $highlightedFields[] = 'email'; $highlightedFields[] = 'external_identifier'; //format custom field names, CRM-2676 @@ -129,15 +129,11 @@ public function preProcess() { if ($this->_onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK) { //Mark Dedupe Rule Fields as required, since it's used in matching contact - foreach (array( - 'Individual', - 'Household', - 'Organization', - ) as $cType) { - $ruleParams = array( + foreach (['Individual', 'Household', 'Organization'] as $cType) { + $ruleParams = [ 'contact_type' => $cType, 'used' => 'Unsupervised', - ); + ]; $this->_dedupeFields[$cType] = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams); } @@ -161,7 +157,7 @@ public function preProcess() { $this->assign('highlightedFields', $highlightedFields); $this->_formattedFieldNames[$contactType] = $this->_mapperFields = array_merge($this->_mapperFields, $formattedFieldNames); - $columnNames = array(); + $columnNames = []; //get original col headers from csv if present. if ($dataSource == 'CRM_Import_DataSource_CSV' && $skipColumnHeader) { $columnNames = $this->get('originalColHeader'); @@ -180,7 +176,7 @@ public function preProcess() { } $showColNames = TRUE; - if ($dataSource == 'CRM_Import_DataSource_CSV' && !$skipColumnHeader) { + if ($dataSource === 'CRM_Import_DataSource_CSV' && !$skipColumnHeader) { $showColNames = FALSE; } $this->assign('showColNames', $showColNames); @@ -197,61 +193,23 @@ public function preProcess() { /** * Build the form object. + * + * @throws \CiviCRM_API3_Exception */ public function buildQuickForm() { - //to save the current mappings - if (!$this->get('savedMapping')) { - $saveDetailsName = ts('Save this field mapping'); - $this->applyFilter('saveMappingName', 'trim'); - $this->add('text', 'saveMappingName', ts('Name')); - $this->add('text', 'saveMappingDesc', ts('Description')); - } - else { - $savedMapping = $this->get('savedMapping'); - - list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingImProvider, $mappingRelation, $mappingOperator, $mappingValue, $mappingWebsiteType) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping); - - //get loaded Mapping Fields - $mappingName = CRM_Utils_Array::value(1, $mappingName); - $mappingContactType = CRM_Utils_Array::value(1, $mappingContactType); - $mappingLocation = CRM_Utils_Array::value(1, $mappingLocation); - $mappingPhoneType = CRM_Utils_Array::value(1, $mappingPhoneType); - $mappingImProvider = CRM_Utils_Array::value(1, $mappingImProvider); - $mappingRelation = CRM_Utils_Array::value(1, $mappingRelation); - $mappingWebsiteType = CRM_Utils_Array::value(1, $mappingWebsiteType); - - $this->assign('loadedMapping', $savedMapping); - $this->set('loadedMapping', $savedMapping); - - $params = array('id' => $savedMapping); - $temp = array(); - $mappingDetails = CRM_Core_BAO_Mapping::retrieve($params, $temp); - - $this->assign('savedName', $mappingDetails->name); - - $this->add('hidden', 'mappingId', $savedMapping); - - $this->addElement('checkbox', 'updateMapping', ts('Update this field mapping'), NULL); - $saveDetailsName = ts('Save as a new field mapping'); - $this->add('text', 'saveMappingName', ts('Name')); - $this->add('text', 'saveMappingDesc', ts('Description')); - } + $savedMappingID = (int) $this->get('savedMapping'); + $this->buildSavedMappingFields($savedMappingID); - $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, array('onclick' => "showSaveDetails(this)")); - - $this->addFormRule(array('CRM_Contact_Import_Form_MapField', 'formRule')); + $this->addFormRule(['CRM_Contact_Import_Form_MapField', 'formRule']); //-------- end of saved mapping stuff --------- - $defaults = array(); + $defaults = []; $mapperKeys = array_keys($this->_mapperFields); $hasColumnNames = !empty($this->_columnNames); - $columnPatterns = $this->get('columnPatterns'); - $dataPatterns = $this->get('dataPatterns'); $hasLocationTypes = $this->get('fieldTypes'); - $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - + $this->_location_types = ['Primary' => ts('Primary')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $defaultLocationType = CRM_Core_BAO_LocationType::getDefault(); // Pass default location to js @@ -284,13 +242,13 @@ public function buildQuickForm() { $contactRelation = new CRM_Contact_DAO_RelationshipType(); $contactRelation->find(); while ($contactRelation->fetch()) { - $contactRelationCache[$contactRelation->id] = array(); + $contactRelationCache[$contactRelation->id] = []; $contactRelationCache[$contactRelation->id]['contact_type_a'] = $contactRelation->contact_type_a; $contactRelationCache[$contactRelation->id]['contact_sub_type_a'] = $contactRelation->contact_sub_type_a; $contactRelationCache[$contactRelation->id]['contact_type_b'] = $contactRelation->contact_type_b; $contactRelationCache[$contactRelation->id]['contact_sub_type_b'] = $contactRelation->contact_sub_type_b; } - $highlightedFields = $highlightedRelFields = array(); + $highlightedFields = $highlightedRelFields = []; $highlightedFields['email'] = 'All'; $highlightedFields['external_identifier'] = 'All'; @@ -307,7 +265,7 @@ public function buildQuickForm() { else { $id = $first = $second = NULL; } - if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) { + if (($first === 'a' && $second === 'b') || ($first === 'b' && $second === 'a')) { $cType = $contactRelationCache[$id]["contact_type_{$second}"]; //CRM-5125 for contact subtype specific relationshiptypes @@ -320,16 +278,15 @@ public function buildQuickForm() { $cType = 'All'; } - $relatedFields = array(); $relatedFields = CRM_Contact_BAO_Contact::importableFields($cType); unset($relatedFields['']); - $values = array(); + $values = []; foreach ($relatedFields as $name => $field) { $values[$name] = $field['title']; if (isset($hasLocationTypes[$name])) { $sel3[$key][$name] = $this->_location_types; } - elseif ($name == 'url') { + elseif ($name === 'url') { $sel3[$key][$name] = $websiteTypes; } else { @@ -349,7 +306,7 @@ public function buildQuickForm() { if ($this->_onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK && !empty($this->_dedupeFields[$cType]) && is_array($this->_dedupeFields[$cType]) ) { - static $cTypeArray = array(); + static $cTypeArray = []; if ($cType != $this->_contactType && !in_array($cType, $cTypeArray)) { foreach ($this->_dedupeFields[$cType] as $val) { if ($valTitle = CRM_Utils_Array::value($val, $this->_formattedFieldNames[$cType])) { @@ -361,7 +318,7 @@ public function buildQuickForm() { } foreach ($highlightedFields as $k => $v) { - if ($v == $cType || $v == 'All') { + if ($v == $cType || $v === 'All') { $highlightedRelFields[$key][] = $k; } } @@ -392,7 +349,7 @@ public function buildQuickForm() { if (!empty($hasLocationTypes[$key])) { $options = $this->_location_types; } - elseif ($key == 'url') { + elseif ($key === 'url') { $options = $websiteTypes; } $sel2[$key] = $options; @@ -402,177 +359,48 @@ public function buildQuickForm() { $js = "\n"; @@ -582,37 +410,27 @@ public function buildQuickForm() { if (isset($mappingName) && ($this->_columnCount != count($mappingName)) ) { - $warning++; - } - - if ($warning != 0 && $this->get('savedMapping')) { - $session = CRM_Core_Session::singleton(); - $session->setStatus(ts('The data columns in this import file appear to be different from the saved mapping. Please verify that you have selected the correct saved mapping before continuing.')); - } - else { - $session = CRM_Core_Session::singleton(); - $session->setStatus(NULL); + CRM_Core_Session::singleton()->setStatus(ts('The data columns in this import file appear to be different from the saved mapping. Please verify that you have selected the correct saved mapping before continuing.')); } $this->setDefaults($defaults); - $this->addButtons(array( - array( - 'type' => 'back', - 'name' => ts('Previous'), - ), - array( - 'type' => 'next', - 'name' => ts('Continue'), - 'spacing' => '          ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'back', + 'name' => ts('Previous'), + ], + [ + 'type' => 'next', + 'name' => ts('Continue'), + 'spacing' => '          ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -625,14 +443,14 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields) { - $errors = array(); + $errors = []; if (!empty($fields['saveMapping'])) { $nameField = CRM_Utils_Array::value('saveMappingName', $fields); if (empty($nameField)) { $errors['saveMappingName'] = ts('Name is required to save Import Mapping'); } else { - $mappingTypeId = CRM_Core_OptionGroup::getValue('mapping_type', 'Import Contact', 'name'); + $mappingTypeId = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Contact'); if (CRM_Core_BAO_Mapping::checkMapping($nameField, $mappingTypeId)) { $errors['saveMappingName'] = ts('Duplicate Import Mapping Name'); } @@ -666,43 +484,69 @@ public function postProcess() { $this->controller->resetPage($this->_name); return; } - - $mapper = array(); - $mapperKeys = array(); $mapperKeys = $this->controller->exportValue($this->_name, 'mapper'); - $mapperKeysMain = array(); + + $parser = $this->submit($params, $mapperKeys); + + // add all the necessary variables to the form + $parser->set($this); + } + + /** + * Format custom field name. + * + * Combine group and field name to avoid conflict. + * + * @param array $fields + * + * @return array + */ + public function formatCustomFieldName($fields) { + //CRM-2676, replacing the conflict for same custom field name from different custom group. + $fieldIds = $formattedFieldNames = []; + foreach ($fields as $key => $value) { + if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) { + $fieldIds[] = $customFieldId; + } + } + + if (!empty($fieldIds) && is_array($fieldIds)) { + $groupTitles = CRM_Core_BAO_CustomGroup::getGroupTitles($fieldIds); + + if (!empty($groupTitles)) { + foreach ($groupTitles as $fId => $values) { + $key = "custom_{$fId}"; + $groupTitle = $values['groupTitle']; + $formattedFieldNames[$key] = $fields[$key] . ' :: ' . $groupTitle; + } + } + } + + return $formattedFieldNames; + } + + /** + * Main submit function. + * + * Extracted to add testing & start refactoring. + * + * @param $params + * @param $mapperKeys + * + * @return \CRM_Contact_Import_Parser_Contact + * @throws \CiviCRM_API3_Exception + */ + public function submit($params, $mapperKeys) { + $mapper = $mapperKeysMain = $locations = []; + $parserParameters = CRM_Contact_Import_Parser_Contact::getParameterForParser($this->_columnCount); $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'); $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'); $websiteTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id'); $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - - //these mapper params need to set key as array and val as null. - $mapperParams = array( - 'related' => 'relatedVal', - 'locations' => 'locationsVal', - 'mapperLocType' => 'mapperLocTypeVal', - 'mapperPhoneType' => 'mapperPhoneTypeVal', - 'mapperImProvider' => 'mapperImProviderVal', - 'mapperWebsiteType' => 'mapperWebsiteTypeVal', - 'relatedContactType' => 'relatedContactTypeVal', - 'relatedContactDetails' => 'relatedContactDetailsVal', - 'relatedContactLocType' => 'relatedContactLocTypeVal', - 'relatedContactPhoneType' => 'relatedContactPhoneTypeVal', - 'relatedContactImProvider' => 'relatedContactImProviderVal', - 'relatedContactWebsiteType' => 'relatedContactWebsiteTypeVal', - ); - - //set respective mapper params to array. - foreach (array_keys($mapperParams) as $mapperParam) { - $$mapperParam = array(); - } + $locationTypes['Primary'] = ts('Primary'); for ($i = 0; $i < $this->_columnCount; $i++) { - //set respective mapper value to null - foreach (array_values($mapperParams) as $mapperParam) { - $$mapperParam = NULL; - } $fldName = CRM_Utils_Array::value(0, $mapperKeys[$i]); $selOne = CRM_Utils_Array::value(1, $mapperKeys[$i]); @@ -712,19 +556,19 @@ public function postProcess() { $mapperKeysMain[$i] = $fldName; //need to differentiate non location elements. - if ($selOne && is_numeric($selOne)) { - if ($fldName == 'url') { - $mapperWebsiteTypeVal = $websiteTypes[$selOne]; + if ($selOne && (is_numeric($selOne) || $selOne === 'Primary')) { + if ($fldName === 'url') { + $parserParameters['mapperWebsiteType'][$i] = $websiteTypes[$selOne]; } else { - $locationsVal = $locationTypes[$selOne]; - $mapperLocTypeVal = $selOne; + $locations[$i] = $locationTypes[$selOne]; + $parserParameters['mapperLocType'][$i] = $selOne; if ($selTwo && is_numeric($selTwo)) { - if ($fldName == 'phone') { - $mapperPhoneTypeVal = $phoneTypes[$selTwo]; + if ($fldName === 'phone') { + $parserParameters['mapperPhoneType'][$i] = $phoneTypes[$selTwo]; } - elseif ($fldName == 'im') { - $mapperImProviderVal = $imProviders[$selTwo]; + elseif ($fldName === 'im') { + $parserParameters['mapperImProvider'][$i] = $imProviders[$selTwo]; } } } @@ -732,22 +576,22 @@ public function postProcess() { //relationship contact mapper info. list($id, $first, $second) = CRM_Utils_System::explode('_', $fldName, 3); - if (($first == 'a' && $second == 'b') || - ($first == 'b' && $second == 'a') + if (($first === 'a' && $second === 'b') || + ($first === 'b' && $second === 'a') ) { - $relatedVal = $this->_mapperFields[$fldName]; + $parserParameters['mapperRelated'][$i] = $this->_mapperFields[$fldName]; if ($selOne) { - if ($selOne == 'url') { - $relatedContactWebsiteTypeVal = $websiteTypes[$selTwo]; + if ($selOne === 'url') { + $parserParameters['relatedContactWebsiteType'][$i] = $websiteTypes[$selTwo]; } else { - $relatedContactLocTypeVal = CRM_Utils_Array::value($selTwo, $locationTypes); + $parserParameters['relatedContactLocType'][$i] = CRM_Utils_Array::value($selTwo, $locationTypes); if ($selThree) { - if ($selOne == 'phone') { - $relatedContactPhoneTypeVal = $phoneTypes[$selThree]; + if ($selOne === 'phone') { + $parserParameters['relatedContactPhoneType'][$i] = $phoneTypes[$selThree]; } - elseif ($selOne == 'im') { - $relatedContactImProviderVal = $imProviders[$selThree]; + elseif ($selOne === 'im') { + $parserParameters['relatedContactImProvider'][$i] = $imProviders[$selThree]; } } } @@ -756,44 +600,25 @@ public function postProcess() { $relationType = new CRM_Contact_DAO_RelationshipType(); $relationType->id = $id; $relationType->find(TRUE); - $relatedContactTypeVal = $relationType->{"contact_type_$second"}; - $relatedContactDetailsVal = $this->_formattedFieldNames[$relatedContactTypeVal][$selOne]; + $parserParameters['relatedContactType'][$i] = $relationType->{"contact_type_$second"}; + $parserParameters['relatedContactDetails'][$i] = $this->_formattedFieldNames[$parserParameters['relatedContactType'][$i]][$selOne]; } } - - //set the respective mapper param array values. - foreach ($mapperParams as $mapperParamKey => $mapperParamVal) { - ${$mapperParamKey}[$i] = $$mapperParamVal; - } } $this->set('columnNames', $this->_columnNames); - - //set main contact properties. - $properties = array( - 'ims' => 'mapperImProvider', - 'mapper' => 'mapper', - 'phones' => 'mapperPhoneType', - 'websites' => 'mapperWebsiteType', - 'locations' => 'locations', - ); - foreach ($properties as $propertyName => $propertyVal) { - $this->set($propertyName, $$propertyVal); - } - - //set related contact propeties. - $relProperties = array( - 'related', - 'relatedContactType', - 'relatedContactDetails', - 'relatedContactLocType', - 'relatedContactPhoneType', - 'relatedContactImProvider', - 'relatedContactWebsiteType', - ); - foreach ($relProperties as $relProperty) { - $this->set($relProperty, $$relProperty); - } + $this->set('websites', $parserParameters['mapperWebsiteType']); + $this->set('locations', $locations); + $this->set('phones', $parserParameters['mapperPhoneType']); + $this->set('ims', $parserParameters['mapperImProvider']); + $this->set('related', $parserParameters['mapperRelated']); + $this->set('relatedContactType', $parserParameters['relatedContactType']); + $this->set('relatedContactDetails', $parserParameters['relatedContactDetails']); + $this->set('relatedContactLocType', $parserParameters['relatedContactLocType']); + $this->set('relatedContactPhoneType', $parserParameters['relatedContactPhoneType']); + $this->set('relatedContactImProvider', $parserParameters['relatedContactImProvider']); + $this->set('relatedContactWebsiteType', $parserParameters['relatedContactWebsiteType']); + $this->set('mapper', $mapper); // store mapping Id to display it in the preview page $this->set('loadMappingId', CRM_Utils_Array::value('mappingId', $params)); @@ -801,13 +626,11 @@ public function postProcess() { //Updating Mapping Records if (!empty($params['updateMapping'])) { - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $mappingFields = new CRM_Core_DAO_MappingField(); $mappingFields->mapping_id = $params['mappingId']; $mappingFields->find(); - $mappingFieldsId = array(); + $mappingFieldsId = []; while ($mappingFields->fetch()) { if ($mappingFields->id) { $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id; @@ -840,7 +663,7 @@ public function postProcess() { elseif (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'im') { $updateMappingFields->im_provider_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL; } - $updateMappingFields->location_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; + $updateMappingFields->location_type_id = isset($mapperKeys[$i][2]) && is_numeric($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; } } else { @@ -859,8 +682,9 @@ public function postProcess() { elseif (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'im') { $updateMappingFields->im_provider_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; } - $location = array_keys($locationTypes, $locations[$i]); - $updateMappingFields->location_type_id = (isset($location) && isset($location[0])) ? $location[0] : NULL; + $locationTypeID = $parserParameters['mapperLocType'][$i]; + // location_type_id is NULL for non-location fields, and for Primary location. + $updateMappingFields->location_type_id = is_numeric($locationTypeID) ? $locationTypeID : 'null'; } } $updateMappingFields->save(); @@ -869,18 +693,14 @@ public function postProcess() { //Saving Mapping Details and Records if (!empty($params['saveMapping'])) { - $mappingParams = array( + $mappingParams = [ 'name' => $params['saveMappingName'], 'description' => $params['saveMappingDesc'], - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Import Contact', - 'name' - ), - ); + 'mapping_type_id' => 'Import Contact', + ]; - $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams); + $saveMapping = civicrm_api3('Mapping', 'create', $mappingParams); - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); $contactType = $this->get('contactType'); switch ($contactType) { case CRM_Import_Parser::CONTACT_INDIVIDUAL: @@ -895,64 +715,18 @@ public function postProcess() { $cType = 'Organization'; } + $mappingID = NULL; for ($i = 0; $i < $this->_columnCount; $i++) { - $saveMappingFields = new CRM_Core_DAO_MappingField(); - $saveMappingFields->mapping_id = $saveMapping->id; - $saveMappingFields->contact_type = $cType; - $saveMappingFields->column_number = $i; - - $mapperKeyParts = explode('_', $mapperKeys[$i][0], 3); - $id = isset($mapperKeyParts[0]) ? $mapperKeyParts[0] : NULL; - $first = isset($mapperKeyParts[1]) ? $mapperKeyParts[1] : NULL; - $second = isset($mapperKeyParts[2]) ? $mapperKeyParts[2] : NULL; - if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) { - $saveMappingFields->name = ucwords(str_replace("_", " ", $mapperKeys[$i][1])); - $saveMappingFields->relationship_type_id = $id; - $saveMappingFields->relationship_direction = "{$first}_{$second}"; - // to get phoneType id and provider id separately - // before saving mappingFields of phone and IM for related contact, CRM-3140 - if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'url') { - $saveMappingFields->website_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; - } - else { - if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'phone') { - $saveMappingFields->phone_type_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL; - } - elseif (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'im') { - $saveMappingFields->im_provider_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL; - } - $saveMappingFields->location_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; - } - } - else { - $saveMappingFields->name = $mapper[$i]; - $location_id = array_keys($locationTypes, $locations[$i]); - // to get phoneType id and provider id separately - // before saving mappingFields of phone and IM, CRM-3140 - if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'url') { - $saveMappingFields->website_type_id = isset($mapperKeys[$i][1]) ? $mapperKeys[$i][1] : NULL; - } - else { - if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'phone') { - $saveMappingFields->phone_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; - } - elseif (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'im') { - $saveMappingFields->im_provider_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; - } - $saveMappingFields->location_type_id = isset($location_id[0]) ? $location_id[0] : NULL; - } - $saveMappingFields->relationship_type_id = NULL; - } - $saveMappingFields->save(); + $mappingID = $this->saveMappingField($mapperKeys, $saveMapping, $cType, $i, $mapper, $parserParameters); } - $this->set('savedMapping', $saveMappingFields->mapping_id); + $this->set('savedMapping', $mappingID); } - $parser = new CRM_Contact_Import_Parser_Contact($mapperKeysMain, $mapperLocType, $mapperPhoneType, - $mapperImProvider, $related, $relatedContactType, - $relatedContactDetails, $relatedContactLocType, - $relatedContactPhoneType, $relatedContactImProvider, - $mapperWebsiteType, $relatedContactWebsiteType + $parser = new CRM_Contact_Import_Parser_Contact($mapperKeysMain, $parserParameters['mapperLocType'], $parserParameters['mapperPhoneType'], + $parserParameters['mapperImProvider'], $parserParameters['mapperRelated'], $parserParameters['relatedContactType'], + $parserParameters['relatedContactDetails'], $parserParameters['relatedContactLocType'], + $parserParameters['relatedContactPhoneType'], $parserParameters['relatedContactImProvider'], + $parserParameters['mapperWebsiteType'], $parserParameters['relatedContactWebsiteType'] ); $primaryKeyName = $this->get('primaryKeyName'); @@ -969,42 +743,69 @@ public function postProcess() { $this->get('contactSubType'), $this->get('dedupe') ); - - // add all the necessary variables to the form - $parser->set($this); + return $parser; } /** - * Format custom field name. - * - * Combine group and field name to avoid conflict. - * - * @param array $fields + * @param $mapperKeys + * @param array $saveMapping + * @param string $cType + * @param int $i + * @param array $mapper + * @param array $parserParameters * - * @return array + * @return int */ - public function formatCustomFieldName(&$fields) { - //CRM-2676, replacing the conflict for same custom field name from different custom group. - $fieldIds = $formattedFieldNames = array(); - foreach ($fields as $key => $value) { - if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) { - $fieldIds[] = $customFieldId; + protected function saveMappingField($mapperKeys, array $saveMapping, string $cType, int $i, array $mapper, array $parserParameters): int { + $saveMappingFields = new CRM_Core_DAO_MappingField(); + $saveMappingFields->mapping_id = $saveMapping['id']; + $saveMappingFields->contact_type = $cType; + $saveMappingFields->column_number = $i; + + $mapperKeyParts = explode('_', $mapperKeys[$i][0], 3); + $id = isset($mapperKeyParts[0]) ? $mapperKeyParts[0] : NULL; + $first = isset($mapperKeyParts[1]) ? $mapperKeyParts[1] : NULL; + $second = isset($mapperKeyParts[2]) ? $mapperKeyParts[2] : NULL; + if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) { + $saveMappingFields->name = ucwords(str_replace("_", " ", $mapperKeys[$i][1])); + $saveMappingFields->relationship_type_id = $id; + $saveMappingFields->relationship_direction = "{$first}_{$second}"; + // to get phoneType id and provider id separately + // before saving mappingFields of phone and IM for related contact, CRM-3140 + if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'url') { + $saveMappingFields->website_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; + } + else { + if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'phone') { + $saveMappingFields->phone_type_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL; + } + elseif (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'im') { + $saveMappingFields->im_provider_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL; + } + $saveMappingFields->location_type_id = (isset($mapperKeys[$i][2]) && $mapperKeys[$i][2] !== 'Primary') ? $mapperKeys[$i][2] : NULL; } } - - if (!empty($fieldIds) && is_array($fieldIds)) { - $groupTitles = CRM_Core_BAO_CustomGroup::getGroupTitles($fieldIds); - - if (!empty($groupTitles)) { - foreach ($groupTitles as $fId => $values) { - $key = "custom_{$fId}"; - $groupTitle = $values['groupTitle']; - $formattedFieldNames[$key] = $fields[$key] . ' :: ' . $groupTitle; + else { + $saveMappingFields->name = $mapper[$i]; + $locationTypeID = $parserParameters['mapperLocType'][$i]; + // to get phoneType id and provider id separately + // before saving mappingFields of phone and IM, CRM-3140 + if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'url') { + $saveMappingFields->website_type_id = isset($mapperKeys[$i][1]) ? $mapperKeys[$i][1] : NULL; + } + else { + if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'phone') { + $saveMappingFields->phone_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; + } + elseif (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'im') { + $saveMappingFields->im_provider_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL; } + $saveMappingFields->location_type_id = is_numeric($locationTypeID) ? $locationTypeID : NULL; } + $saveMappingFields->relationship_type_id = NULL; } - - return $formattedFieldNames; + $saveMappingFields->save(); + return $saveMappingFields->mapping_id; } } diff --git a/CRM/Contact/Import/Form/Preview.php b/CRM/Contact/Import/Form/Preview.php index 26c8e14641b0..e47a5a10474a 100644 --- a/CRM/Contact/Import/Form/Preview.php +++ b/CRM/Contact/Import/Form/Preview.php @@ -1,9 +1,9 @@ assign($property, $this->get($property)); } - $statusID = $this->get('statusID'); - if (!$statusID) { - $statusID = md5(uniqid(rand(), TRUE)); - $this->set('statusID', $statusID); - } - $statusUrl = CRM_Utils_System::url('civicrm/ajax/status', "id={$statusID}", FALSE, NULL, FALSE); - $this->assign('statusUrl', $statusUrl); + $this->setStatusUrl(); $showColNames = TRUE; if ('CRM_Import_DataSource_CSV' == $this->get('dataSource') && @@ -157,9 +151,9 @@ public function buildQuickForm() { if (!empty($groups)) { $this->addElement('select', 'groups', ts('Add imported records to existing group(s)'), $groups, array( - 'multiple' => "multiple", - 'class' => 'crm-select2', - )); + 'multiple' => "multiple", + 'class' => 'crm-select2', + )); } //display new tag @@ -193,7 +187,6 @@ public function buildQuickForm() { 'name' => ts('Import Now'), 'spacing' => '          ', 'isDefault' => TRUE, - 'js' => array('onclick' => "return verify( );"), ), array( 'type' => 'cancel', @@ -306,7 +299,7 @@ public function postProcess() { // Clear all caches, forcing any searches to recheck the ACLs or group membership as the import // may have changed it. - CRM_Contact_BAO_Contact_Utils::clearContactCaches(); + CRM_Contact_BAO_Contact_Utils::clearContactCaches(TRUE); // add all the necessary variables to the form $importJob->setFormVariables($this); diff --git a/CRM/Contact/Import/Form/Summary.php b/CRM/Contact/Import/Form/Summary.php index 031f94c2dc2c..811b3f62e22f 100644 --- a/CRM/Contact/Import/Form/Summary.php +++ b/CRM/Contact/Import/Form/Summary.php @@ -1,9 +1,9 @@ assign('dupeActionString', $dupeActionString); - $properties = array( + $properties = [ 'totalRowCount', 'validRowCount', 'invalidRowCount', @@ -110,7 +110,7 @@ public function preProcess() { 'tagAdditions', 'unMatchCount', 'unparsedAddressCount', - ); + ]; foreach ($properties as $property) { $this->assign($property, $this->get($property)); } diff --git a/CRM/Contact/Import/ImportJob.php b/CRM/Contact/Import/ImportJob.php index 7c278e6380c6..fff23875c7a7 100644 --- a/CRM/Contact/Import/ImportJob.php +++ b/CRM/Contact/Import/ImportJob.php @@ -1,9 +1,9 @@ _tableName = $tableName; - - //initialize the properties. - $properties = array( - 'mapperKeys', - 'mapperRelated', - 'mapperLocTypes', - 'mapperPhoneTypes', - 'mapperImProviders', - 'mapperWebsiteTypes', - 'mapperRelatedContactType', - 'mapperRelatedContactDetails', - 'mapperRelatedContactLocType', - 'mapperRelatedContactPhoneType', - 'mapperRelatedContactImProvider', - 'mapperRelatedContactWebsiteType', - ); - foreach ($properties as $property) { - $this->{"_$property"} = array(); - } } /** @@ -169,31 +139,13 @@ public function setJobParams(&$params) { public function runImport(&$form, $timeout = 55) { $mapper = $this->_mapper; $mapperFields = array(); + $parserParameters = CRM_Contact_Import_Parser_Contact::getParameterForParser(count($mapper)); $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'); $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'); $websiteTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id'); - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - - //initialize mapper perperty value. - $mapperPeroperties = array( - 'mapperRelated' => 'mapperRelatedVal', - 'mapperLocTypes' => 'mapperLocTypesVal', - 'mapperPhoneTypes' => 'mapperPhoneTypesVal', - 'mapperImProviders' => 'mapperImProvidersVal', - 'mapperWebsiteTypes' => 'mapperWebsiteTypesVal', - 'mapperRelatedContactType' => 'mapperRelatedContactTypeVal', - 'mapperRelatedContactDetails' => 'mapperRelatedContactDetailsVal', - 'mapperRelatedContactLocType' => 'mapperRelatedContactLocTypeVal', - 'mapperRelatedContactPhoneType' => 'mapperRelatedContactPhoneTypeVal', - 'mapperRelatedContactImProvider' => 'mapperRelatedContactImProviderVal', - 'mapperRelatedContactWebsiteType' => 'mapperRelatedContactWebsiteTypeVal', - ); + $locationTypes = array('Primary' => ts('Primary')) + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); foreach ($mapper as $key => $value) { - //set respective mapper value to null. - foreach (array_values($mapperPeroperties) as $perpertyVal) { - $$perpertyVal = NULL; - } $fldName = CRM_Utils_Array::value(0, $mapper[$key]); $header = array($this->_mapFields[$fldName]); @@ -203,22 +155,23 @@ public function runImport(&$form, $timeout = 55) { $this->_mapperKeys[$key] = $fldName; //need to differentiate non location elements. - if ($selOne && is_numeric($selOne)) { + // @todo merge this with duplicate code on MapField class. + if ($selOne && (is_numeric($selOne) || $selOne === 'Primary')) { if ($fldName == 'url') { $header[] = $websiteTypes[$selOne]; - $mapperWebsiteTypesVal = $selOne; + $parserParameters['mapperWebsiteType'][$key] = $selOne; } else { $header[] = $locationTypes[$selOne]; - $mapperLocTypesVal = $selOne; + $parserParameters['mapperLocType'][$key] = $selOne; if ($selTwo && is_numeric($selTwo)) { if ($fldName == 'phone') { $header[] = $phoneTypes[$selTwo]; - $mapperPhoneTypesVal = $selTwo; + $parserParameters['mapperPhoneType'][$key] = $selTwo; } elseif ($fldName == 'im') { $header[] = $imProviders[$selTwo]; - $mapperImProvidersVal = $selTwo; + $parserParameters['mapperImProvider'][$key] = $selTwo; } } } @@ -237,27 +190,27 @@ public function runImport(&$form, $timeout = 55) { $relationType = new CRM_Contact_DAO_RelationshipType(); $relationType->id = $id; $relationType->find(TRUE); - $mapperRelatedContactTypeVal = $relationType->{"contact_type_$second"}; + $parserParameters['relatedContactType'][$key] = $relationType->{"contact_type_$second"}; - $mapperRelatedVal = $fldName; + $parserParameters['mapperRelated'][$key] = $fldName; if ($selOne) { - $mapperRelatedContactDetailsVal = $selOne; + $parserParameters['relatedContactDetails'][$key] = $selOne; if ($selTwo) { if ($selOne == 'url') { $header[] = $websiteTypes[$selTwo]; - $mapperRelatedContactWebsiteTypeVal = $selTwo; + $parserParameters[$key]['relatedContactWebsiteType'][$key] = $selTwo; } else { $header[] = $locationTypes[$selTwo]; - $mapperRelatedContactLocTypeVal = $selTwo; + $parserParameters['relatedContactLocType'][$key] = $selTwo; if ($selThree) { if ($selOne == 'phone') { $header[] = $phoneTypes[$selThree]; - $mapperRelatedContactPhoneTypeVal = $selThree; + $parserParameters['relatedContactPhoneType'][$key] = $selThree; } elseif ($selOne == 'im') { $header[] = $imProviders[$selThree]; - $mapperRelatedContactImProviderVal = $selThree; + $parserParameters['relatedContactImProvider'][$key] = $selThree; } } } @@ -265,26 +218,21 @@ public function runImport(&$form, $timeout = 55) { } } $mapperFields[] = implode(' - ', $header); - - //set the respective mapper param array values. - foreach ($mapperPeroperties as $mapperProKey => $mapperProVal) { - $this->{"_$mapperProKey"}[$key] = $$mapperProVal; - } } $this->_parser = new CRM_Contact_Import_Parser_Contact( $this->_mapperKeys, - $this->_mapperLocTypes, - $this->_mapperPhoneTypes, - $this->_mapperImProviders, - $this->_mapperRelated, - $this->_mapperRelatedContactType, - $this->_mapperRelatedContactDetails, - $this->_mapperRelatedContactLocType, - $this->_mapperRelatedContactPhoneType, - $this->_mapperRelatedContactImProvider, - $this->_mapperWebsiteTypes, - $this->_mapperRelatedContactWebsiteType + $parserParameters['mapperLocType'], + $parserParameters['mapperPhoneType'], + $parserParameters['mapperImProvider'], + $parserParameters['mapperRelated'], + $parserParameters['relatedContactType'], + $parserParameters['relatedContactDetails'], + $parserParameters['relatedContactLocType'], + $parserParameters['relatedContactPhoneType'], + $parserParameters['relatedContactImProvider'], + $parserParameters['mapperWebsiteType'], + $parserParameters['relatedContactWebsiteType'] ); $this->_parser->run($this->_tableName, $mapperFields, @@ -323,7 +271,7 @@ public function runImport(&$form, $timeout = 55) { } } - if ($this->_newTagName || count($this->_tag)) { + if ($this->_newTagName || !empty($this->_tag)) { $tagAdditions = $this->_tagImportedContactsWithNewTag($contactIds, $this->_newTagName, $this->_newTagDesc @@ -468,7 +416,7 @@ public static function getIncompleteImportTables() { $result = CRM_Core_DAO::executeQuery($query, array($database)); $incompleteImportTables = array(); while ($importTable = $result->fetch()) { - if (!$this->isComplete($importTable)) { + if (!self::isComplete($importTable)) { $incompleteImportTables[] = $importTable; } } diff --git a/CRM/Contact/Import/Importer.php b/CRM/Contact/Import/Importer.php index 6252e8ceecd2..779d994b6ebe 100644 --- a/CRM/Contact/Import/Importer.php +++ b/CRM/Contact/Import/Importer.php @@ -1,9 +1,9 @@ getContactType() . $this->getContactSubType(); + if (Civi::cache('fields')->has($cacheKey)) { + return Civi::cache('fields')->get($cacheKey); + } + $contactFields = CRM_Contact_BAO_Contact::importableFields($this->getContactType()); + // exclude the address options disabled in the Address Settings + $fields = CRM_Core_BAO_Address::validateAddressOptions($contactFields); + + //CRM-5125 + //supporting import for contact subtypes + $csType = NULL; + if ($this->getContactSubType()) { + //custom fields for sub type + $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($this->getContactSubType()); + + if (!empty($subTypeFields)) { + foreach ($subTypeFields as $customSubTypeField => $details) { + $fields[$customSubTypeField] = $details; + } + } + } + + foreach ($this->getRelationships() as $key => $var) { + list($type) = explode('_', $key); + $relationshipType[$key]['title'] = $var; + $relationshipType[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; + $relationshipType[$key]['import'] = TRUE; + $relationshipType[$key]['relationship_type_id'] = $type; + $relationshipType[$key]['related'] = TRUE; + } + + if (!empty($relationshipType)) { + $fields = array_merge($fields, [ + 'related' => [ + 'title' => ts('- related contact info -'), + ], + ], $relationshipType); + } + Civi::cache('fields')->set($cacheKey, $fields); + return $fields; + } + + /** + * Get sorted available relationships. + * + * @return array + */ + protected function getRelationships(): array { + $cacheKey = 'importable_contact_relationship_field_metadata' . $this->getContactType() . $this->getContactSubType(); + if (Civi::cache('fields')->has($cacheKey)) { + return Civi::cache('fields')->get($cacheKey); + } + //Relationship importables + $relations = CRM_Contact_BAO_Relationship::getContactRelationshipType( + NULL, NULL, NULL, $this->getContactType(), + FALSE, 'label', TRUE, $this->getContactSubType() + ); + asort($relations); + Civi::cache('fields')->set($cacheKey, $relations); + return $relations; + } + + /** + * Get an array of header patterns for importable keys. + * + * @return array + */ + public function getHeaderPatterns() { + return CRM_Utils_Array::collect('headerPattern', $this->getContactImportMetadata()); + } + + /** + * Get an array of header patterns for importable keys. + * + * @return array + */ + public function getDataPatterns() { + return CRM_Utils_Array::collect('dataPattern', $this->getContactImportMetadata()); + } + + /** + * Get an array of header patterns for importable keys. + * + * @return array + */ + public function getFieldTitles() { + return CRM_Utils_Array::collect('title', $this->getContactImportMetadata()); + } + + /** + * Get configured contact type. + */ + protected function getContactType() { + return $this->_contactType ?? 'Individual'; + } + + /** + * Get configured contact sub type. + * + * @return string + */ + protected function getContactSubType() { + return $this->_contactSubType ?? NULL; + } + +} diff --git a/CRM/Contact/Import/Page/AJAX.php b/CRM/Contact/Import/Page/AJAX.php index 04d9d3772296..94a9e0afd3ce 100644 --- a/CRM/Contact/Import/Page/AJAX.php +++ b/CRM/Contact/Import/Page/AJAX.php @@ -1,9 +1,9 @@   " . ts('No processing status reported yet.') . ""; - echo json_encode(array(0, $status)); + echo json_encode([0, $status]); } CRM_Utils_System::civiExit(); } diff --git a/CRM/Contact/Import/Parser.php b/CRM/Contact/Import/Parser.php index 06dbb8978a09..067582ace351 100644 --- a/CRM/Contact/Import/Parser.php +++ b/CRM/Contact/Import/Parser.php @@ -1,9 +1,9 @@ _invalidRowCount = $this->_validCount = 0; $this->_totalCount = $this->_conflictCount = 0; - $this->_errors = array(); - $this->_warnings = array(); - $this->_conflicts = array(); - $this->_unparsedAddresses = array(); + $this->_errors = []; + $this->_warnings = []; + $this->_conflicts = []; + $this->_unparsedAddresses = []; $this->_tableName = $tableName; $this->_primaryKeyName = $primaryKeyName; $this->_statusFieldName = $statusFieldName; if ($mode == self::MODE_MAPFIELD) { - $this->_rows = array(); + $this->_rows = []; } else { $this->_activeFieldCount = count($this->_activeFields); @@ -167,30 +173,19 @@ public function run( } if ($statusID) { - $skip = 50; - // $skip = 1; - $config = CRM_Core_Config::singleton(); - $statusFile = "{$config->uploadDir}status_{$statusID}.txt"; - $status = "
      " . ts('No processing status reported yet.') . "
    "; - - //do not force the browser to display the save dialog, CRM-7640 - $contents = json_encode(array(0, $status)); - - file_put_contents($statusFile, $contents); - + $this->progressImport($statusID); $startTimestamp = $currTimestamp = $prevTimestamp = time(); } - // get the contents of the temp. import table $query = "SELECT * FROM $tableName"; if ($mode == self::MODE_IMPORT) { $query .= " WHERE $statusFieldName = 'NEW'"; } - $dao = new CRM_Core_DAO(); - $db = $dao->getDatabaseConnection(); - $result = $db->query($query); - while ($values = $result->fetchRow(DB_FETCHMODE_ORDERED)) { + $result = CRM_Core_DAO::executeQuery($query); + + while ($result->fetch()) { + $values = array_values($result->toArray()); $this->_rowCount++; /* trim whitespace around the values */ @@ -215,39 +210,9 @@ public function run( elseif ($mode == self::MODE_IMPORT) { //print "Running parser in import mode
    \n"; $returnCode = $this->import($onDuplicate, $values, $doGeocodeAddress); - if ($statusID && (($this->_rowCount % $skip) == 0)) { - $currTimestamp = time(); - $totalTime = ($currTimestamp - $startTimestamp); - $time = ($currTimestamp - $prevTimestamp); - $recordsLeft = $totalRowCount - $this->_rowCount; - if ($recordsLeft < 0) { - $recordsLeft = 0; - } - $estimatedTime = ($recordsLeft / $skip) * $time; - $estMinutes = floor($estimatedTime / 60); - $timeFormatted = ''; - if ($estMinutes > 1) { - $timeFormatted = $estMinutes . ' ' . ts('minutes') . ' '; - $estimatedTime = $estimatedTime - ($estMinutes * 60); - } - $timeFormatted .= round($estimatedTime) . ' ' . ts('seconds'); - $processedPercent = (int ) (($this->_rowCount * 100) / $totalRowCount); - $statusMsg = ts('%1 of %2 records - %3 remaining', - array(1 => $this->_rowCount, 2 => $totalRowCount, 3 => $timeFormatted) - ); - $status = " -
    {$statusMsg} -
    -"; - - $contents = json_encode(array($processedPercent, $status)); - - file_put_contents($statusFile, $contents); - - $prevTimestamp = $currTimestamp; + if ($statusID && (($this->_rowCount % 50) == 0)) { + $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount); } - // sleep(1); } else { $returnCode = self::ERROR; @@ -271,10 +236,8 @@ public function run( if ($returnCode & self::ERROR) { $this->_invalidRowCount++; - if ($this->_invalidRowCount < $this->_maxErrorCount) { - array_unshift($values, $this->_rowCount); - $this->_errors[] = $values; - } + array_unshift($values, $this->_rowCount); + $this->_errors[] = $values; } if ($returnCode & self::CONFLICT) { @@ -318,9 +281,6 @@ public function run( break; } - // clean up memory from dao's - CRM_Core_DAO::freeResult(); - // see if we've hit our timeout yet /* if ( $the_thing_with_the_stuff ) { do_something( ); @@ -339,54 +299,44 @@ public function run( if ($this->_invalidRowCount) { // removed view url for invlaid contacts - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_errorFileName = self::errorFileName(self::ERROR); self::exportCSV($this->_errorFileName, $headers, $this->_errors); } if ($this->_conflictCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_conflictFileName = self::errorFileName(self::CONFLICT); self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts); } if ($this->_duplicateCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('View Contact URL'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('View Contact URL'), + ], $customHeaders); $this->_duplicateFileName = self::errorFileName(self::DUPLICATE); self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates); } if ($this->_unMatchCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_misMatchFilemName = self::errorFileName(self::NO_MATCH); self::exportCSV($this->_misMatchFilemName, $headers, $this->_unMatch); } if ($this->_unparsedAddressCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('Contact Edit URL'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Contact Edit URL'), + ], $customHeaders); $this->_errorFileName = self::errorFileName(self::UNPARSED_ADDRESS_WARNING); self::exportCSV($this->_errorFileName, $headers, $this->_unparsedAddresses); } @@ -426,6 +376,7 @@ public function setActiveFieldLocationTypes($elements) { /** * @param $elements */ + /** * @param $elements */ @@ -531,7 +482,7 @@ public function setActiveFieldRelatedContactImProvider($elements) { * (reference ) associative array of name/value pairs */ public function &getActiveFieldParams() { - $params = array(); + $params = []; for ($i = 0; $i < $this->_activeFieldCount; $i++) { if ($this->_activeFields[$i]->_name == 'do_not_import') { @@ -541,13 +492,13 @@ public function &getActiveFieldParams() { if (isset($this->_activeFields[$i]->_value)) { if (isset($this->_activeFields[$i]->_hasLocationType)) { if (!isset($params[$this->_activeFields[$i]->_name])) { - $params[$this->_activeFields[$i]->_name] = array(); + $params[$this->_activeFields[$i]->_name] = []; } - $value = array( + $value = [ $this->_activeFields[$i]->_name => $this->_activeFields[$i]->_value, 'location_type_id' => $this->_activeFields[$i]->_hasLocationType, - ); + ]; if (isset($this->_activeFields[$i]->_phoneType)) { $value['phone_type_id'] = $this->_activeFields[$i]->_phoneType; @@ -561,10 +512,10 @@ public function &getActiveFieldParams() { $params[$this->_activeFields[$i]->_name][] = $value; } elseif (isset($this->_activeFields[$i]->_websiteType)) { - $value = array( + $value = [ $this->_activeFields[$i]->_name => $this->_activeFields[$i]->_value, 'website_type_id' => $this->_activeFields[$i]->_websiteType, - ); + ]; $params[$this->_activeFields[$i]->_name][] = $value; } @@ -578,7 +529,7 @@ public function &getActiveFieldParams() { //minor fix for CRM-4062 if (isset($this->_activeFields[$i]->_related)) { if (!isset($params[$this->_activeFields[$i]->_related])) { - $params[$this->_activeFields[$i]->_related] = array(); + $params[$this->_activeFields[$i]->_related] = []; } if (!isset($params[$this->_activeFields[$i]->_related]['contact_type']) && !empty($this->_activeFields[$i]->_relatedContactType)) { @@ -589,12 +540,12 @@ public function &getActiveFieldParams() { if (!empty($params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails]) && !is_array($params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails]) ) { - $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = array(); + $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = []; } - $value = array( + $value = [ $this->_activeFields[$i]->_relatedContactDetails => $this->_activeFields[$i]->_value, 'location_type_id' => $this->_activeFields[$i]->_relatedContactLocType, - ); + ]; if (isset($this->_activeFields[$i]->_relatedContactPhoneType)) { $value['phone_type_id'] = $this->_activeFields[$i]->_relatedContactPhoneType; @@ -608,10 +559,10 @@ public function &getActiveFieldParams() { $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = $value; } elseif (isset($this->_activeFields[$i]->_relatedContactWebsiteType)) { - $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = array( + $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails][] = [ 'url' => $this->_activeFields[$i]->_value, 'website_type_id' => $this->_activeFields[$i]->_relatedContactWebsiteType, - ); + ]; } else { $params[$this->_activeFields[$i]->_related][$this->_activeFields[$i]->_relatedContactDetails] = $this->_activeFields[$i]->_value; @@ -627,7 +578,8 @@ public function &getActiveFieldParams() { * @return array */ public function getColumnPatterns() { - $values = array(); + CRM_Core_Error::deprecatedFunctionWarning('no longer used- use CRM_Contact_Import_MetadataTrait'); + $values = []; foreach ($this->_fields as $name => $field) { $values[$name] = $field->_columnPattern; } @@ -665,8 +617,6 @@ public function set($store, $mode = self::MODE_SUMMARY) { $store->set('fields', $this->getSelectValues()); $store->set('fieldTypes', $this->getSelectTypes()); - $store->set('columnPatterns', $this->getColumnPatterns()); - $store->set('dataPatterns', $this->getDataPatterns()); $store->set('columnCount', $this->_activeFieldCount); $store->set('totalRowCount', $this->_totalCount); @@ -728,8 +678,8 @@ public static function exportCSV($fileName, $header, $data) { CRM_Core_Error::movedSiteError($fileName); } //hack to remove '_status', '_statusMsg' and '_id' from error file - $errorValues = array(); - $dbRecordStatus = array('IMPORTED', 'ERROR', 'DUPLICATE', 'INVALID', 'NEW'); + $errorValues = []; + $dbRecordStatus = ['IMPORTED', 'ERROR', 'DUPLICATE', 'INVALID', 'NEW']; foreach ($data as $rowCount => $rowValues) { $count = 0; foreach ($rowValues as $key => $val) { @@ -742,7 +692,7 @@ public static function exportCSV($fileName, $header, $data) { } $data = $errorValues; - $output = array(); + $output = []; $fd = fopen($fileName, 'w'); foreach ($header as $key => $value) { @@ -779,11 +729,11 @@ public function updateImportRecord($id, &$params) { SET $statusFieldName = ?, ${statusFieldName}Msg = ? WHERE $primaryKeyName = ?"; - $args = array( + $args = [ $params[$statusFieldName], CRM_Utils_Array::value("${statusFieldName}Msg", $params), $id, - ); + ]; //print "Running query: $query
    With arguments: ".$params[$statusFieldName].", ".$params["${statusFieldName}Msg"].", $id
    "; @@ -791,4 +741,624 @@ public function updateImportRecord($id, &$params) { } } + /** + * Format common params data to proper format to store. + * + * @param array $params + * Contain record values. + * @param array $formatted + * Array of formatted data. + * @param array $contactFields + * Contact DAO fields. + */ + public function formatCommonData($params, &$formatted, &$contactFields) { + $csType = [ + CRM_Utils_Array::value('contact_type', $formatted), + ]; + + //CRM-5125 + //add custom fields for contact sub type + if (!empty($this->_contactSubType)) { + $csType = $this->_contactSubType; + } + + if ($relCsType = CRM_Utils_Array::value('contact_sub_type', $formatted)) { + $csType = $relCsType; + } + + $customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $csType); + + $addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address'); + $customFields = $customFields + $addressCustomFields; + + //if a Custom Email Greeting, Custom Postal Greeting or Custom Addressee is mapped, and no "Greeting / Addressee Type ID" is provided, then automatically set the type = Customized, CRM-4575 + $elements = [ + 'email_greeting_custom' => 'email_greeting', + 'postal_greeting_custom' => 'postal_greeting', + 'addressee_custom' => 'addressee', + ]; + foreach ($elements as $k => $v) { + if (array_key_exists($k, $params) && !(array_key_exists($v, $params))) { + $label = key(CRM_Core_OptionGroup::values($v, TRUE, NULL, NULL, 'AND v.name = "Customized"')); + $params[$v] = $label; + } + } + + //format date first + $session = CRM_Core_Session::singleton(); + $dateType = $session->get("dateTypes"); + foreach ($params as $key => $val) { + $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); + if ($customFieldID && + !array_key_exists($customFieldID, $addressCustomFields) + ) { + //we should not update Date to null, CRM-4062 + if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) { + //CRM-21267 + CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key); + } + elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { + if (empty($val) && !is_numeric($val) && $this->_onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) { + //retain earlier value when Import mode is `Fill` + unset($params[$key]); + } + else { + $params[$key] = CRM_Utils_String::strtoboolstr($val); + } + } + } + + if ($key == 'birth_date' && $val) { + CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key); + } + elseif ($key == 'deceased_date' && $val) { + CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key); + $params['is_deceased'] = 1; + } + elseif ($key == 'is_deceased' && $val) { + $params[$key] = CRM_Utils_String::strtoboolstr($val); + } + } + + //now format custom data. + foreach ($params as $key => $field) { + if (is_array($field)) { + $isAddressCustomField = FALSE; + foreach ($field as $value) { + $break = FALSE; + if (is_array($value)) { + foreach ($value as $name => $testForEmpty) { + if ($addressCustomFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) { + $isAddressCustomField = TRUE; + break; + } + // check if $value does not contain IM provider or phoneType + if (($name !== 'phone_type_id' || $name !== 'provider_id') && ($testForEmpty === '' || $testForEmpty == NULL)) { + $break = TRUE; + break; + } + } + } + else { + $break = TRUE; + } + + if (!$break) { + if (!empty($value['location_type_id'])) { + $this->formatLocationBlock($value, $formatted); + } + else { + CRM_Core_Error::deprecatedFunctionWarning('this is not expected to be reachable now'); + $this->formatContactParameters($value, $formatted); + } + } + } + if (!$isAddressCustomField) { + continue; + } + } + + $formatValues = [ + $key => $field, + ]; + + if (($key !== 'preferred_communication_method') && (array_key_exists($key, $contactFields))) { + // due to merging of individual table and + // contact table, we need to avoid + // preferred_communication_method forcefully + $formatValues['contact_type'] = $formatted['contact_type']; + } + + if ($key == 'id' && isset($field)) { + $formatted[$key] = $field; + } + $this->formatContactParameters($formatValues, $formatted); + + //Handling Custom Data + // note: Address custom fields will be handled separately inside formatContactParameters + if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && + array_key_exists($customFieldID, $customFields) && + !array_key_exists($customFieldID, $addressCustomFields) + ) { + + $extends = CRM_Utils_Array::value('extends', $customFields[$customFieldID]); + $htmlType = CRM_Utils_Array::value('html_type', $customFields[$customFieldID]); + switch ($htmlType) { + case 'Select': + case 'Radio': + case 'Autocomplete-Select': + if ($customFields[$customFieldID]['data_type'] == 'String' || $customFields[$customFieldID]['data_type'] == 'Int') { + $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); + foreach ($customOption as $customValue) { + $val = CRM_Utils_Array::value('value', $customValue); + $label = CRM_Utils_Array::value('label', $customValue); + $label = strtolower($label); + $value = strtolower(trim($formatted[$key])); + if (($value == $label) || ($value == strtolower($val))) { + $params[$key] = $formatted[$key] = $val; + } + } + } + break; + + case 'CheckBox': + case 'Multi-Select': + + if (!empty($formatted[$key]) && !empty($params[$key])) { + $mulValues = explode(',', $formatted[$key]); + $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); + $formatted[$key] = []; + $params[$key] = []; + foreach ($mulValues as $v1) { + foreach ($customOption as $v2) { + if ((strtolower($v2['label']) == strtolower(trim($v1))) || + (strtolower($v2['value']) == strtolower(trim($v1))) + ) { + if ($htmlType == 'CheckBox') { + $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1; + } + else { + $params[$key][] = $formatted[$key][] = $v2['value']; + } + } + } + } + } + break; + } + } + } + + if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && array_key_exists($customFieldID, $customFields) && + !array_key_exists($customFieldID, $addressCustomFields) + ) { + // @todo calling api functions directly is not supported + _civicrm_api3_custom_format_params($params, $formatted, $extends); + } + + // to check if not update mode and unset the fields with empty value. + if (!$this->_updateWithId && array_key_exists('custom', $formatted)) { + foreach ($formatted['custom'] as $customKey => $customvalue) { + if (empty($formatted['custom'][$customKey][-1]['is_required'])) { + $formatted['custom'][$customKey][-1]['is_required'] = $customFields[$customKey]['is_required']; + } + $emptyValue = CRM_Utils_Array::value('value', $customvalue[-1]); + if (!isset($emptyValue)) { + unset($formatted['custom'][$customKey]); + } + } + } + + // parse street address, CRM-5450 + if ($this->_parseStreetAddress) { + if (array_key_exists('address', $formatted) && is_array($formatted['address'])) { + foreach ($formatted['address'] as $instance => & $address) { + $streetAddress = CRM_Utils_Array::value('street_address', $address); + if (empty($streetAddress)) { + continue; + } + // parse address field. + $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($streetAddress); + + //street address consider to be parsed properly, + //If we get street_name and street_number. + if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) { + $parsedFields = array_fill_keys(array_keys($parsedFields), ''); + } + + // merge parse address w/ main address block. + $address = array_merge($address, $parsedFields); + } + } + } + } + + /** + * Format contact parameters. + * + * @todo this function needs re-writing & re-merging into the main function. + * + * Here be dragons. + * + * @param array $values + * @param array $params + * + * @return bool + */ + protected function formatContactParameters(&$values, &$params) { + // Crawl through the possible classes: + // Contact + // Individual + // Household + // Organization + // Location + // Address + // Email + // Phone + // IM + // Note + // Custom + + // first add core contact values since for other Civi modules they are not added + $contactFields = CRM_Contact_DAO_Contact::fields(); + _civicrm_api3_store_values($contactFields, $values, $params); + + if (isset($values['contact_type'])) { + // we're an individual/household/org property + + $fields[$values['contact_type']] = CRM_Contact_DAO_Contact::fields(); + + _civicrm_api3_store_values($fields[$values['contact_type']], $values, $params); + return TRUE; + } + + // Cache the various object fields + // @todo - remove this after confirming this is just a compilation of other-wise-cached fields. + static $fields = []; + + if (isset($values['individual_prefix'])) { + if (!empty($params['prefix_id'])) { + $prefixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id'); + $params['prefix'] = $prefixes[$params['prefix_id']]; + } + else { + $params['prefix'] = $values['individual_prefix']; + } + return TRUE; + } + + if (isset($values['individual_suffix'])) { + if (!empty($params['suffix_id'])) { + $suffixes = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id'); + $params['suffix'] = $suffixes[$params['suffix_id']]; + } + else { + $params['suffix'] = $values['individual_suffix']; + } + return TRUE; + } + + // CRM-4575 + if (isset($values['email_greeting'])) { + if (!empty($params['email_greeting_id'])) { + $emailGreetingFilter = [ + 'contact_type' => CRM_Utils_Array::value('contact_type', $params), + 'greeting_type' => 'email_greeting', + ]; + $emailGreetings = CRM_Core_PseudoConstant::greeting($emailGreetingFilter); + $params['email_greeting'] = $emailGreetings[$params['email_greeting_id']]; + } + else { + $params['email_greeting'] = $values['email_greeting']; + } + + return TRUE; + } + + if (isset($values['postal_greeting'])) { + if (!empty($params['postal_greeting_id'])) { + $postalGreetingFilter = [ + 'contact_type' => CRM_Utils_Array::value('contact_type', $params), + 'greeting_type' => 'postal_greeting', + ]; + $postalGreetings = CRM_Core_PseudoConstant::greeting($postalGreetingFilter); + $params['postal_greeting'] = $postalGreetings[$params['postal_greeting_id']]; + } + else { + $params['postal_greeting'] = $values['postal_greeting']; + } + return TRUE; + } + + if (isset($values['addressee'])) { + if (!empty($params['addressee_id'])) { + $addresseeFilter = [ + 'contact_type' => CRM_Utils_Array::value('contact_type', $params), + 'greeting_type' => 'addressee', + ]; + $addressee = CRM_Core_PseudoConstant::addressee($addresseeFilter); + $params['addressee'] = $addressee[$params['addressee_id']]; + } + else { + $params['addressee'] = $values['addressee']; + } + return TRUE; + } + + if (isset($values['gender'])) { + if (!empty($params['gender_id'])) { + $genders = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'); + $params['gender'] = $genders[$params['gender_id']]; + } + else { + $params['gender'] = $values['gender']; + } + return TRUE; + } + + if (!empty($values['preferred_communication_method'])) { + $comm = []; + $pcm = array_change_key_case(array_flip(CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method')), CASE_LOWER); + + $preffComm = explode(',', $values['preferred_communication_method']); + foreach ($preffComm as $v) { + $v = strtolower(trim($v)); + if (array_key_exists($v, $pcm)) { + $comm[$pcm[$v]] = 1; + } + } + + $params['preferred_communication_method'] = $comm; + return TRUE; + } + + // format the website params. + if (!empty($values['url'])) { + static $websiteFields; + if (!is_array($websiteFields)) { + $websiteFields = CRM_Core_DAO_Website::fields(); + } + if (!array_key_exists('website', $params) || + !is_array($params['website']) + ) { + $params['website'] = []; + } + + $websiteCount = count($params['website']); + _civicrm_api3_store_values($websiteFields, $values, + $params['website'][++$websiteCount] + ); + + return TRUE; + } + + // get the formatted location blocks into params - w/ 3.0 format, CRM-4605 + if (!empty($values['location_type_id'])) { + CRM_Core_Error::deprecatedFunctionWarning('this is not expected to be reachable now'); + return $this->formatLocationBlock($values, $params); + } + + if (isset($values['note'])) { + // add a note field + if (!isset($params['note'])) { + $params['note'] = []; + } + $noteBlock = count($params['note']) + 1; + + $params['note'][$noteBlock] = []; + if (!isset($fields['Note'])) { + $fields['Note'] = CRM_Core_DAO_Note::fields(); + } + + // get the current logged in civicrm user + $session = CRM_Core_Session::singleton(); + $userID = $session->get('userID'); + + if ($userID) { + $values['contact_id'] = $userID; + } + + _civicrm_api3_store_values($fields['Note'], $values, $params['note'][$noteBlock]); + + return TRUE; + } + + // Check for custom field values + $customFields = CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $values), + FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE + ); + + foreach ($values as $key => $value) { + if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { + // check if it's a valid custom field id + + if (!array_key_exists($customFieldID, $customFields)) { + return civicrm_api3_create_error('Invalid custom field ID'); + } + else { + $params[$key] = $value; + } + } + } + return TRUE; + } + + /** + * Format location block ready for importing. + * + * There is some test coverage for this in CRM_Contact_Import_Parser_ContactTest + * e.g. testImportPrimaryAddress. + * + * @param array $values + * @param array $params + * + * @return bool + */ + protected function formatLocationBlock(&$values, &$params) { + $blockTypes = [ + 'phone' => 'Phone', + 'email' => 'Email', + 'im' => 'IM', + 'openid' => 'OpenID', + 'phone_ext' => 'Phone', + ]; + foreach ($blockTypes as $blockFieldName => $block) { + if (!array_key_exists($blockFieldName, $values)) { + continue; + } + $blockIndex = $values['location_type_id'] . (!empty($values['phone_type_id']) ? '_' . $values['phone_type_id'] : ''); + + // block present in value array. + if (!array_key_exists($blockFieldName, $params) || !is_array($params[$blockFieldName])) { + $params[$blockFieldName] = []; + } + + $fields[$block] = $this->getMetadataForEntity($block); + + // copy value to dao field name. + if ($blockFieldName == 'im') { + $values['name'] = $values[$blockFieldName]; + } + + _civicrm_api3_store_values($fields[$block], $values, + $params[$blockFieldName][$blockIndex] + ); + + $this->fillPrimary($params[$blockFieldName][$blockIndex], $values, $block, CRM_Utils_Array::value('id', $params)); + + if (empty($params['id']) && (count($params[$blockFieldName]) == 1)) { + $params[$blockFieldName][$blockIndex]['is_primary'] = TRUE; + } + + // we only process single block at a time. + return TRUE; + } + + // handle address fields. + if (!array_key_exists('address', $params) || !is_array($params['address'])) { + $params['address'] = []; + } + + // Note: we doing multiple value formatting here for address custom fields, plus putting into right format. + // The actual formatting (like date, country ..etc) for address custom fields is taken care of while saving + // the address in CRM_Core_BAO_Address::create method + if (!empty($values['location_type_id'])) { + static $customFields = []; + if (empty($customFields)) { + $customFields = CRM_Core_BAO_CustomField::getFields('Address'); + } + // make a copy of values, as we going to make changes + $newValues = $values; + foreach ($values as $key => $val) { + $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); + if ($customFieldID && array_key_exists($customFieldID, $customFields)) { + + $htmlType = CRM_Utils_Array::value('html_type', $customFields[$customFieldID]); + switch ($htmlType) { + case 'CheckBox': + case 'Multi-Select': + if ($val) { + $mulValues = explode(',', $val); + $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); + $newValues[$key] = []; + foreach ($mulValues as $v1) { + foreach ($customOption as $v2) { + if ((strtolower($v2['label']) == strtolower(trim($v1))) || + (strtolower($v2['value']) == strtolower(trim($v1))) + ) { + if ($htmlType == 'CheckBox') { + $newValues[$key][$v2['value']] = 1; + } + else { + $newValues[$key][] = $v2['value']; + } + } + } + } + } + break; + } + } + } + // consider new values + $values = $newValues; + } + + $fields['Address'] = $this->getMetadataForEntity('Address'); + // @todo this is kinda replicated below.... + _civicrm_api3_store_values($fields['Address'], $values, $params['address'][$values['location_type_id']]); + + $addressFields = [ + 'county', + 'country', + 'state_province', + 'supplemental_address_1', + 'supplemental_address_2', + 'supplemental_address_3', + 'StateProvince.name', + ]; + foreach (array_keys($customFields) as $customFieldID) { + $addressFields[] = 'custom_' . $customFieldID; + } + + foreach ($addressFields as $field) { + if (array_key_exists($field, $values)) { + if (!array_key_exists('address', $params)) { + $params['address'] = []; + } + $params['address'][$values['location_type_id']][$field] = $values[$field]; + } + } + + $this->fillPrimary($params['address'][$values['location_type_id']], $values, 'address', CRM_Utils_Array::value('id', $params)); + return TRUE; + } + + /** + * Get the field metadata for the relevant entity. + * + * @param string $entity + * + * @return array + */ + protected function getMetadataForEntity($entity) { + if (!isset($this->fieldMetadata[$entity])) { + $className = "CRM_Core_DAO_$entity"; + $this->fieldMetadata[$entity] = $className::fields(); + } + return $this->fieldMetadata[$entity]; + } + + /** + * Fill in the primary location. + * + * If the contact has a primary address we update it. Otherwise + * we add an address of the default location type. + * + * @param array $params + * Address block parameters + * @param array $values + * Input values + * @param string $entity + * - address, email, phone + * @param int|null $contactID + * + * @throws \CiviCRM_API3_Exception + */ + protected function fillPrimary(&$params, $values, $entity, $contactID) { + if ($values['location_type_id'] === 'Primary') { + if ($contactID) { + $primary = civicrm_api3($entity, 'get', [ + 'return' => 'location_type_id', + 'contact_id' => $contactID, + 'is_primary' => 1, + 'sequential' => 1, + ]); + } + $defaultLocationType = CRM_Core_BAO_LocationType::getDefault(); + $params['location_type_id'] = (int) (isset($primary) && $primary['count']) ? $primary['values'][0]['location_type_id'] : $defaultLocationType->id; + $params['is_primary'] = 1; + } + } + } diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php index aa4a3fde5d21..1002b8adcbcc 100644 --- a/CRM/Contact/Import/Parser/Contact.php +++ b/CRM/Contact/Import/Parser/Contact.php @@ -1,9 +1,9 @@ _mapperKeys = &$mapperKeys; + $this->_mapperKeys = $mapperKeys; $this->_mapperLocType = &$mapperLocType; $this->_mapperPhoneType = &$mapperPhoneType; $this->_mapperWebsiteType = $mapperWebsiteType; @@ -144,54 +149,12 @@ public function __construct( * The initializer code, called before processing. */ public function init() { - $contactFields = CRM_Contact_BAO_Contact::importableFields($this->_contactType); - // exclude the address options disabled in the Address Settings - $fields = CRM_Core_BAO_Address::validateAddressOptions($contactFields); - - //CRM-5125 - //supporting import for contact subtypes - $csType = NULL; - if (!empty($this->_contactSubType)) { - //custom fields for sub type - $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($this->_contactSubType); - - if (!empty($subTypeFields)) { - foreach ($subTypeFields as $customSubTypeField => $details) { - $fields[$customSubTypeField] = $details; - } - } - } - - //Relationship importables - $this->_relationships = $relations - = CRM_Contact_BAO_Relationship::getContactRelationshipType( - NULL, NULL, NULL, $this->_contactType, - FALSE, 'label', TRUE, $this->_contactSubType - ); - asort($relations); - - foreach ($relations as $key => $var) { - list($type) = explode('_', $key); - $relationshipType[$key]['title'] = $var; - $relationshipType[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; - $relationshipType[$key]['import'] = TRUE; - $relationshipType[$key]['relationship_type_id'] = $type; - $relationshipType[$key]['related'] = TRUE; - } - - if (!empty($relationshipType)) { - $fields = array_merge($fields, array( - 'related' => array( - 'title' => ts('- related contact info -'), - ), - ), $relationshipType); - } - - foreach ($fields as $name => $field) { + $this->setFieldMetadata(); + foreach ($this->getImportableFieldsMetadata() as $name => $field) { $this->addField($name, $field['title'], CRM_Utils_Array::value('type', $field), CRM_Utils_Array::value('headerPattern', $field), CRM_Utils_Array::value('dataPattern', $field), CRM_Utils_Array::value('hasLocationType', $field)); } - $this->_newContacts = array(); + $this->_newContacts = []; $this->setActiveFields($this->_mapperKeys); $this->setActiveFieldLocationTypes($this->_mapperLocType); @@ -222,7 +185,7 @@ public function init() { foreach ($this->_mapperKeys as $key) { if (substr($key, 0, 5) == 'email' && substr($key, 0, 14) != 'email_greeting') { $this->_emailIndex = $index; - $this->_allEmails = array(); + $this->_allEmails = []; } if (substr($key, 0, 5) == 'phone') { $this->_phoneIndex = $index; @@ -242,17 +205,16 @@ public function init() { if ($key == 'external_identifier') { $this->_externalIdentifierIndex = $index; - $this->_allExternalIdentifiers = array(); + $this->_allExternalIdentifiers = []; } $index++; } $this->_updateWithId = FALSE; - if (in_array('id', $this->_mapperKeys) || ($this->_externalIdentifierIndex >= 0 && in_array($this->_onDuplicate, array( - CRM_Import_Parser::DUPLICATE_UPDATE, - CRM_Import_Parser::DUPLICATE_FILL, - ))) - ) { + if (in_array('id', $this->_mapperKeys) || ($this->_externalIdentifierIndex >= 0 && in_array($this->_onDuplicate, [ + CRM_Import_Parser::DUPLICATE_UPDATE, + CRM_Import_Parser::DUPLICATE_FILL, + ]))) { $this->_updateWithId = TRUE; } @@ -301,7 +263,7 @@ public function summary(&$values) { $errorRequired = FALSE; switch ($this->_contactType) { case 'Individual': - $missingNames = array(); + $missingNames = []; if ($this->_firstNameIndex < 0 || empty($values[$this->_firstNameIndex])) { $errorRequired = TRUE; $missingNames[] = ts('First Name'); @@ -345,10 +307,10 @@ public function summary(&$values) { $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address'); } array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; @@ -362,10 +324,10 @@ public function summary(&$values) { if (!CRM_Utils_Rule::email($email)) { $errorMessage = ts('Invalid Email address'); array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; @@ -383,10 +345,10 @@ public function summary(&$values) { $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address'); } array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; @@ -398,12 +360,12 @@ public function summary(&$values) { /* If it's a dupe,external Identifier */ if ($externalDupe = CRM_Utils_Array::value($externalID, $this->_allExternalIdentifiers)) { - $errorMessage = ts('External ID conflicts with record %1', array(1 => $externalDupe)); + $errorMessage = ts('External ID conflicts with record %1', [1 => $externalDupe]); array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; } @@ -433,10 +395,10 @@ public function summary(&$values) { if ($errorMessage) { $tempMsg = "Invalid value for field(s) : $errorMessage"; // put the error message in the import record in the DB - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $tempMsg, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); array_unshift($values, $tempMsg); $errorMessage = NULL; @@ -446,14 +408,24 @@ public function summary(&$values) { //if user correcting errors by walking back //need to reset status ERROR msg to null //now currently we are having valid data. - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'NEW', - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::VALID; } + /** + * Get Array of all the fields that could potentially be part + * import process + * + * @return array + */ + public function getAllFields() { + return $this->_fields; + } + /** * Handle the values in import mode. * @@ -466,13 +438,16 @@ public function summary(&$values) { * * @return bool * the result of this processing + * + * @throws \CiviCRM_API3_Exception + * @throws \CRM_Core_Exception */ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $config = CRM_Core_Config::singleton(); - $this->_unparsedStreetAddressContacts = array(); + $this->_unparsedStreetAddressContacts = []; if (!$doGeocodeAddress) { // CRM-5854, reset the geocode method to null to prevent geocoding - $config->geocodeMethod = NULL; + CRM_Utils_GeocodeProvider::disableForSession(); } // first make sure this is a valid line @@ -481,41 +456,57 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $statusFieldName = $this->_statusFieldName; if ($response != CRM_Import_Parser::VALID) { - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'INVALID', "${statusFieldName}Msg" => "Invalid (Error Code: $response)", - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return $response; } $params = &$this->getActiveFieldParams(); - $formatted = array( + $formatted = [ 'contact_type' => $this->_contactType, - ); + ]; - static $contactFields = NULL; - if ($contactFields == NULL) { - $contactFields = CRM_Contact_DAO_Contact::import(); - } + $contactFields = CRM_Contact_DAO_Contact::import(); //check if external identifier exists in database - if (!empty($params['external_identifier']) && (!empty($params['id']) || in_array($onDuplicate, array( - CRM_Import_Parser::DUPLICATE_SKIP, - CRM_Import_Parser::DUPLICATE_NOCHECK, - ))) - ) { + if (!empty($params['external_identifier']) && (!empty($params['id']) || in_array($onDuplicate, [ + CRM_Import_Parser::DUPLICATE_SKIP, + CRM_Import_Parser::DUPLICATE_NOCHECK, + ]))) { - if ($internalCid = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params['external_identifier'], 'id', 'external_identifier', TRUE)) { + $extIDResult = civicrm_api3('Contact', 'get', [ + 'external_identifier' => $params['external_identifier'], + 'showAll' => 'all', + 'return' => ['id', 'contact_is_deleted'], + ]); + if (isset($extIDResult['id'])) { + // record with matching external identifier does exist. + $internalCid = $extIDResult['id']; if ($internalCid != CRM_Utils_Array::value('id', $params)) { - $errorMessage = ts('External ID already exists in Database.'); - array_unshift($values, $errorMessage); - $importRecordParams = array( - $statusFieldName => 'ERROR', - "${statusFieldName}Msg" => $errorMessage, - ); - $this->updateImportRecord($values[count($values) - 1], $importRecordParams); - return CRM_Import_Parser::DUPLICATE; + if ($extIDResult['values'][$internalCid]['contact_is_deleted'] == 1) { + // And it is deleted. What to do? If we skip it, they user + // will be under the impression that the record exists in + // the database, yet they won't be able to find it. If we + // don't skip it, the database will try to insert a new record + // with an external_identifier that is non-unique. So... + // we will update this contact to remove the external_identifier + // and let a new record be created. + $update_params = ['id' => $internalCid, 'external_identifier' => '']; + civicrm_api3('Contact', 'create', $update_params); + } + else { + $errorMessage = ts('External ID already exists in Database.'); + array_unshift($values, $errorMessage); + $importRecordParams = [ + $statusFieldName => 'ERROR', + "${statusFieldName}Msg" => $errorMessage, + ]; + $this->updateImportRecord($values[count($values) - 1], $importRecordParams); + return CRM_Import_Parser::DUPLICATE; + } } } } @@ -539,7 +530,20 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // Get contact id to format common data in update/fill mode, // prioritising a dedupe rule check over an external_identifier check, but falling back on ext id. if ($this->_updateWithId && empty($params['id'])) { - $possibleMatches = $this->getPossibleContactMatches($params); + try { + $possibleMatches = $this->getPossibleContactMatches($params); + } + catch (CRM_Core_Exception $e) { + $errorMessage = $e->getMessage(); + array_unshift($values, $errorMessage); + + $importRecordParams = [ + $statusFieldName => 'ERROR', + "${statusFieldName}Msg" => $errorMessage, + ]; + $this->updateImportRecord($values[count($values) - 1], $importRecordParams); + return CRM_Import_Parser::ERROR; + } foreach ($possibleMatches as $possibleID) { $params['id'] = $formatted['id'] = $possibleID; } @@ -558,7 +562,12 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // check for the contact_id & type. $error = _civicrm_api3_deprecated_duplicate_formatted_contact($formatted); if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) { - $matchedIDs = explode(',', $error['error_message']['params'][0]); + if (is_array($error['error_message']['params'][0])) { + $matchedIDs = $error['error_message']['params'][0]; + } + else { + $matchedIDs = explode(',', $error['error_message']['params'][0]); + } if (count($matchedIDs) >= 1) { $updateflag = TRUE; foreach ($matchedIDs as $contactId) { @@ -661,7 +670,7 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { if ($createNewContact || ($this->_retCode != CRM_Import_Parser::NO_MATCH && $this->_updateWithId)) { //CRM-4430, don't carry if not submitted. - foreach (array('prefix_id', 'suffix_id', 'gender_id') as $name) { + foreach (['prefix_id', 'suffix_id', 'gender_id'] as $name) { if (!empty($formatted[$name])) { $options = CRM_Contact_BAO_Contact::buildOptions($name, 'get'); if (!isset($options[$formatted[$name]])) { @@ -686,22 +695,24 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $this->_retCode = CRM_Import_Parser::VALID; } } - elseif (isset($newContact) && CRM_Core_Error::isAPIError($newContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) { + elseif (isset($newContact) && CRM_Core_Error::isAPIError($newContact, CRM_Core_Error::DUPLICATE_CONTACT)) { // if duplicate, no need of further processing if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) { $errorMessage = "Skipping duplicate record"; array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'DUPLICATE', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::DUPLICATE; } $relationship = TRUE; - # see CRM-10433 - might return comma separate list of all dupes - $dupeContactIDs = explode(',', $newContact['error_message']['params'][0]); + // CRM-10433/CRM-20739 - IDs could be string or array; handle accordingly + if (!is_array($dupeContactIDs = $newContact['error_message']['params'][0])) { + $dupeContactIDs = explode(',', $dupeContactIDs); + } $dupeCount = count($dupeContactIDs); $contactID = array_pop($dupeContactIDs); // check to see if we had more than one duplicate contact id. @@ -718,13 +729,13 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // call import hook $currentImportID = end($values); - $hookParams = array( + $hookParams = [ 'contactID' => $contactID, 'importID' => $currentImportID, 'importTempTable' => $this->_tableName, 'fieldHeaders' => $this->_mapperKeys, 'fields' => $this->_activeFields, - ); + ]; CRM_Utils_Hook::import('Contact', 'process', $this, $hookParams); } @@ -732,8 +743,8 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { if ($relationship) { $primaryContactId = NULL; if (CRM_Core_Error::isAPIError($newContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) { - if (CRM_Utils_Rule::integer($newContact['error_message']['params'][0])) { - $primaryContactId = $newContact['error_message']['params'][0]; + if ($dupeCount == 1 && CRM_Utils_Rule::integer($contactID)) { + $primaryContactId = $contactID; } } else { @@ -754,9 +765,9 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $relationType->find(TRUE); $direction = "contact_sub_type_$second"; - $formatting = array( + $formatting = [ 'contact_type' => $params[$key]['contact_type'], - ); + ]; //set subtype for related contact CRM-5125 if (isset($relationType->$direction)) { @@ -770,7 +781,6 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $formatting['contact_sub_type'] = $relationType->$direction; } } - $relationType->free(); $contactFields = NULL; $contactFields = CRM_Contact_DAO_Contact::import(); @@ -780,14 +790,13 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $params[$key]['id'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['external_identifier'], 'id', 'external_identifier'); } // check for valid related contact id in update/fill mode, CRM-4424 - if (in_array($onDuplicate, array( - CRM_Import_Parser::DUPLICATE_UPDATE, - CRM_Import_Parser::DUPLICATE_FILL, - )) && !empty($params[$key]['id']) - ) { + if (in_array($onDuplicate, [ + CRM_Import_Parser::DUPLICATE_UPDATE, + CRM_Import_Parser::DUPLICATE_FILL, + ]) && !empty($params[$key]['id'])) { $relatedContactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['id'], 'contact_type'); if (!$relatedContactType) { - $errorMessage = ts("No contact found for this related contact ID: %1", array(1 => $params[$key]['id'])); + $errorMessage = ts("No contact found for this related contact ID: %1", [1 => $params[$key]['id']]); array_unshift($values, $errorMessage); return CRM_Import_Parser::NO_MATCH; } @@ -802,7 +811,7 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($params[$key]['id'], $relatedCsType) && $relatedCsType != CRM_Utils_Array::value('contact_sub_type', $formatting)) ) { - $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.") . ' ' . ts("ID: %1", array(1 => $params[$key]['id'])); + $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.") . ' ' . ts("ID: %1", [1 => $params[$key]['id']]); array_unshift($values, $errorMessage); return CRM_Import_Parser::NO_MATCH; } @@ -828,10 +837,10 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { //fixed for CRM-4148 if (!empty($params[$key]['id'])) { - $contact = array( + $contact = [ 'contact_id' => $params[$key]['id'], - ); - $defaults = array(); + ]; + $defaults = []; $relatedNewContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults); } else { @@ -842,20 +851,23 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $relatedNewContact = clone($relatedNewContact); } - $matchedIDs = array(); + $matchedIDs = []; // To update/fill contact, get the matching contact Ids if duplicate contact found // otherwise get contact Id from object of related contact if (is_array($relatedNewContact) && civicrm_error($relatedNewContact)) { if (CRM_Core_Error::isAPIError($relatedNewContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) { - $matchedIDs = explode(',', $relatedNewContact['error_message']['params'][0]); + $matchedIDs = $relatedNewContact['error_message']['params'][0]; + if (!is_array($matchedIDs)) { + $matchedIDs = explode(',', $matchedIDs); + } } else { $errorMessage = $relatedNewContact['error_message']; array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; } @@ -864,10 +876,10 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $matchedIDs[] = $relatedNewContact->id; } // update/fill related contact after getting matching Contact Ids, CRM-4424 - if (in_array($onDuplicate, array( + if (in_array($onDuplicate, [ CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::DUPLICATE_FILL, - ))) { + ])) { //validation of related contact subtype for update mode //CRM-5125 $relatedCsType = NULL; @@ -884,7 +896,7 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $updatedContact = $this->createContact($formatting, $contactFields, $onDuplicate, $matchedIDs[0]); } } - static $relativeContact = array(); + static $relativeContact = []; if (CRM_Core_Error::isAPIError($relatedNewContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) { if (count($matchedIDs) >= 1) { $relContactId = $matchedIDs[0]; @@ -913,21 +925,21 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { //if more than one duplicate contact //found, create relationship with first contact // now create the relationship record - $relationParams = array(); - $relationParams = array( + $relationParams = []; + $relationParams = [ 'relationship_type_id' => $key, - 'contact_check' => array( + 'contact_check' => [ $relContactId => 1, - ), + ], 'is_active' => 1, 'skipRecentView' => TRUE, - ); + ]; // we only handle related contact success, we ignore failures for now // at some point wold be nice to have related counts as separate - $relationIds = array( + $relationIds = [ 'contact' => $primaryContactId, - ); + ]; list($valid, $invalid, $duplicate, $saved, $relationshipIds) = CRM_Contact_BAO_Relationship::legacyCreateMultiple($relationParams, $relationIds); @@ -940,13 +952,13 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { //handle current employer, CRM-3532 if ($valid) { $allRelationships = CRM_Core_PseudoConstant::relationshipType('name'); - $relationshipTypeId = str_replace(array( + $relationshipTypeId = str_replace([ '_a_b', '_b_a', - ), array( + ], [ '', '', - ), $key); + ], $key); $relationshipType = str_replace($relationshipTypeId . '_', '', $key); $orgId = $individualId = NULL; if ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employee of') { @@ -976,7 +988,7 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $code = NULL; if (($code = CRM_Utils_Array::value('code', $newContact['error_message'])) && ($code == CRM_Core_Error::DUPLICATE_CONTACT)) { - $urls = array(); + $urls = []; // need to fix at some stage and decide if the error will return an // array or string, crude hack for now if (is_array($newContact['error_message']['params'][0])) { @@ -995,10 +1007,10 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // If we duplicate more than one record, skip no matter what if (count($cids) > 1) { $errorMessage = ts('Record duplicates multiple contacts'); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; //combine error msg to avoid mismatch between error file columns. $errorMessage .= "\n" . $url_string; @@ -1011,7 +1023,7 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { $contactId = array_shift($cids); $cid = NULL; - $vals = array('contact_id' => $contactId); + $vals = ['contact_id' => $contactId]; if ($onDuplicate == CRM_Import_Parser::DUPLICATE_REPLACE) { civicrm_api('contact', 'delete', $vals); @@ -1025,10 +1037,10 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { } // else skip does nothing and just returns an error code. if ($cid) { - $contact = array( + $contact = [ 'contact_id' => $cid, - ); - $defaults = array(); + ]; + $defaults = []; $newContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults); } @@ -1037,15 +1049,18 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // different kind of error other than DUPLICATE $errorMessage = $newContact['error_message']; array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; } $contactID = $newContact['error_message']['params'][0]; + if (is_array($contactID)) { + $contactID = array_pop($contactID); + } if (!in_array($contactID, $this->_newContacts)) { $this->_newContacts[] = $contactID; } @@ -1053,17 +1068,17 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { //CRM-262 No Duplicate Checking if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) { array_unshift($values, $url_string); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'DUPLICATE', "${statusFieldName}Msg" => "Skipping duplicate record", - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::DUPLICATE; } - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'IMPORTED', - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); //return warning if street address is not parsed, CRM-5886 return $this->processMessage($values, $statusFieldName, CRM_Import_Parser::VALID); @@ -1072,10 +1087,10 @@ public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) { // Not a dupe, so we had an error $errorMessage = $newContact['error_message']; array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $this->updateImportRecord($values[count($values) - 1], $importRecordParams); return CRM_Import_Parser::ERROR; } @@ -1119,8 +1134,7 @@ public function fini() { * @param null $relationships */ public static function isErrorInCustomData($params, &$errorMessage, $csType = NULL, $relationships = NULL) { - $session = CRM_Core_Session::singleton(); - $dateType = $session->get("dateTypes"); + $dateType = CRM_Core_Session::singleton()->get("dateTypes"); if (!empty($params['contact_sub_type'])) { $csType = CRM_Utils_Array::value('contact_sub_type', $params); @@ -1131,14 +1145,14 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU } // get array of subtypes - CRM-18708 - if (in_array($csType, array('Individual', 'Organization', 'Household'))) { + if (in_array($csType, ['Individual', 'Organization', 'Household'])) { $csType = self::getSubtypes($params['contact_type']); } if (is_array($csType)) { // fetch custom fields for every subtype and add it to $customFields array // CRM-18708 - $customFields = array(); + $customFields = []; foreach ($csType as $cType) { $customFields += CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, FALSE, $cType); } @@ -1186,15 +1200,14 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU } } // need not check for label filed import - $htmlType = array( + $htmlType = [ 'CheckBox', 'Multi-Select', - 'AdvMulti-Select', 'Select', 'Radio', 'Multi-Select State/Province', 'Multi-Select Country', - ); + ]; if (!in_array($customFields[$customFieldID]['html_type'], $htmlType) || $customFields[$customFieldID]['data_type'] == 'Boolean' || $customFields[$customFieldID]['data_type'] == 'ContactReference') { $valid = CRM_Core_BAO_CustomValue::typecheck($customFields[$customFieldID]['data_type'], $value); if (!$valid) { @@ -1203,7 +1216,7 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU } // check for values for custom fields for checkboxes and multiselect - if ($customFields[$customFieldID]['html_type'] == 'CheckBox' || $customFields[$customFieldID]['html_type'] == 'AdvMulti-Select' || $customFields[$customFieldID]['html_type'] == 'Multi-Select') { + if ($customFields[$customFieldID]['html_type'] == 'CheckBox' || $customFields[$customFieldID]['html_type'] == 'Multi-Select') { $value = trim($value); $value = str_replace('|', ',', $value); $mulValues = explode(',', $value); @@ -1259,11 +1272,11 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU $limitCodes = CRM_Core_BAO_Country::countryLimit(); $error = TRUE; - foreach (array( - $countryNames, - $countryIsoCodes, - $limitCodes, - ) as $values) { + foreach ([ + $countryNames, + $countryIsoCodes, + $limitCodes, + ] as $values) { if (in_array(trim($countryValue), $values)) { $error = FALSE; break; @@ -1300,7 +1313,6 @@ public static function isErrorInCustomData($params, &$errorMessage, $csType = NU $params[$key]['contact_sub_type'] = $relationshipType->$direction; } } - $relationshipType->free(); } self::isErrorInCustomData($params[$key], $errorMessage, $csType, $relationships); @@ -1385,7 +1397,6 @@ public function isErrorInCoreData($params, &$errorMessage) { } break; - case 'gender': case 'gender_id': if (!self::checkGender($value)) { self::addToErrorMsg(ts('Gender'), $errorMessage); @@ -1393,7 +1404,7 @@ public function isErrorInCoreData($params, &$errorMessage) { break; case 'preferred_communication_method': - $preffComm = array(); + $preffComm = []; $preffComm = explode(',', $value); foreach ($preffComm as $v) { if (!self::in_value(trim($v), CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method'))) { @@ -1515,30 +1526,30 @@ public function isErrorInCoreData($params, &$errorMessage) { //custom email/postal greeting, custom addressee, CRM-4575 case 'email_greeting': - $emailGreetingFilter = array( + $emailGreetingFilter = [ 'contact_type' => $this->_contactType, 'greeting_type' => 'email_greeting', - ); + ]; if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($emailGreetingFilter))) { self::addToErrorMsg(ts('Email Greeting must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Email Greetings for valid values'), $errorMessage); } break; case 'postal_greeting': - $postalGreetingFilter = array( + $postalGreetingFilter = [ 'contact_type' => $this->_contactType, 'greeting_type' => 'postal_greeting', - ); + ]; if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($postalGreetingFilter))) { self::addToErrorMsg(ts('Postal Greeting must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Postal Greetings for valid values'), $errorMessage); } break; case 'addressee': - $addresseeFilter = array( + $addresseeFilter = [ 'contact_type' => $this->_contactType, 'greeting_type' => 'addressee', - ); + ]; if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($addresseeFilter))) { self::addToErrorMsg(ts('Addressee must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Addressee for valid values'), $errorMessage); } @@ -1635,6 +1646,10 @@ public static function in_value($value, $valueArray) { /** * Build error-message containing error-fields * + * Once upon a time there was a dev who hadn't heard of implode. That dev wrote this function. + * + * @todo just say no! + * * @param string $errorName * A string containing error-field name. * @param string $errorMessage @@ -1673,7 +1688,6 @@ public function createContact(&$formatted, &$contactFields, $onDuplicate, $conta //get the prefix id etc if exists CRM_Contact_BAO_Contact::resolveDefaults($formatted, TRUE); - require_once 'CRM/Utils/DeprecatedUtils.php'; //@todo direct call to API function not supported. // setting required check to false, CRM-2839 // plus we do our own required check in import @@ -1690,17 +1704,16 @@ public function createContact(&$formatted, &$contactFields, $onDuplicate, $conta $this->formatParams($formatted, $onDuplicate, (int) $contactId); } - // pass doNotResetCache flag since resetting and rebuilding cache could be expensive. - $config = CRM_Core_Config::singleton(); - $config->doNotResetCache = 1; + // Resetting and rebuilding cache could be expensive. + CRM_Core_Config::setPermitCacheFlushMode(FALSE); $cid = CRM_Contact_BAO_Contact::createProfileContact($formatted, $contactFields, $contactId, NULL, NULL, $formatted['contact_type']); - $config->doNotResetCache = 0; + CRM_Core_Config::setPermitCacheFlushMode(TRUE); - $contact = array( + $contact = [ 'contact_id' => $cid, - ); + ]; - $defaults = array(); + $defaults = []; $newContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults); } @@ -1708,10 +1721,10 @@ public function createContact(&$formatted, &$contactFields, $onDuplicate, $conta if ($this->_parseStreetAddress && is_object($newContact) && property_exists($newContact, 'address') && $newContact->address) { foreach ($newContact->address as $address) { if (!empty($address['street_address']) && (empty($address['street_number']) || empty($address['street_name']))) { - $this->_unparsedStreetAddressContacts[] = array( + $this->_unparsedStreetAddressContacts[] = [ 'id' => $newContact->id, 'streetAddress' => $address['street_address'], - ); + ]; } } } @@ -1733,11 +1746,11 @@ public function formatParams(&$params, $onDuplicate, $cid) { return; } - $contactParams = array( + $contactParams = [ 'contact_id' => $cid, - ); + ]; - $defaults = array(); + $defaults = []; $contactObj = CRM_Contact_BAO_Contact::retrieve($contactParams, $defaults); $modeUpdate = $modeFill = FALSE; @@ -1750,16 +1763,16 @@ public function formatParams(&$params, $onDuplicate, $cid) { $modeFill = TRUE; } - $groupTree = CRM_Core_BAO_CustomGroup::getTree($params['contact_type'], CRM_Core_DAO::$_nullObject, $cid, 0, NULL); + $groupTree = CRM_Core_BAO_CustomGroup::getTree($params['contact_type'], NULL, $cid, 0, NULL); CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults, FALSE, FALSE); - $locationFields = array( + $locationFields = [ 'email' => 'email', 'phone' => 'phone', 'im' => 'name', 'website' => 'website', 'address' => 'address', - ); + ]; $contact = get_object_vars($contactObj); @@ -1771,23 +1784,28 @@ public function formatParams(&$params, $onDuplicate, $cid) { if (array_key_exists($key, $locationFields)) { continue; } - elseif (in_array($key, array( + elseif (in_array($key, [ 'email_greeting', 'postal_greeting', 'addressee', - ))) { + ])) { // CRM-4575, need to null custom if ($params["{$key}_id"] != 4) { $params["{$key}_custom"] = 'null'; } unset($params[$key]); } - elseif ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) { - $custom = TRUE; - } else { - $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $key); - + if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) { + $custom_params = ['id' => $contact['id'], 'return' => $key]; + $getValue = civicrm_api3('Contact', 'getvalue', $custom_params); + if (empty($getValue)) { + unset($getValue); + } + } + else { + $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $key); + } if ($key == 'contact_source') { $params['source'] = $params[$key]; unset($params[$key]); @@ -1795,6 +1813,11 @@ public function formatParams(&$params, $onDuplicate, $cid) { if ($modeFill && isset($getValue)) { unset($params[$key]); + if ($customFieldId) { + // Extra values must be unset to ensure the values are not + // imported. + unset($params['custom'][$customFieldId]); + } } } } @@ -1846,237 +1869,6 @@ public static function formatCustomDate(&$params, &$formatted, $dateType, $dateP $formatted[$dateParam] = CRM_Utils_Date::processDate($params[$dateParam]); } - /** - * Format common params data to proper format to store. - * - * @param array $params - * Contain record values. - * @param array $formatted - * Array of formatted data. - * @param array $contactFields - * Contact DAO fields. - */ - public function formatCommonData($params, &$formatted, &$contactFields) { - $csType = array( - CRM_Utils_Array::value('contact_type', $formatted), - ); - - //CRM-5125 - //add custom fields for contact sub type - if (!empty($this->_contactSubType)) { - $csType = $this->_contactSubType; - } - - if ($relCsType = CRM_Utils_Array::value('contact_sub_type', $formatted)) { - $csType = $relCsType; - } - - $customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $csType); - - $addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address'); - $customFields = $customFields + $addressCustomFields; - - //if a Custom Email Greeting, Custom Postal Greeting or Custom Addressee is mapped, and no "Greeting / Addressee Type ID" is provided, then automatically set the type = Customized, CRM-4575 - $elements = array( - 'email_greeting_custom' => 'email_greeting', - 'postal_greeting_custom' => 'postal_greeting', - 'addressee_custom' => 'addressee', - ); - foreach ($elements as $k => $v) { - if (array_key_exists($k, $params) && !(array_key_exists($v, $params))) { - $label = key(CRM_Core_OptionGroup::values($v, TRUE, NULL, NULL, 'AND v.name = "Customized"')); - $params[$v] = $label; - } - } - - //format date first - $session = CRM_Core_Session::singleton(); - $dateType = $session->get("dateTypes"); - foreach ($params as $key => $val) { - $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); - if ($customFieldID && - !array_key_exists($customFieldID, $addressCustomFields) - ) { - //we should not update Date to null, CRM-4062 - if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) { - self::formatCustomDate($params, $formatted, $dateType, $key); - } - elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { - if (empty($val) && !is_numeric($val) && $this->_onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) { - //retain earlier value when Import mode is `Fill` - unset($params[$key]); - } - else { - $params[$key] = CRM_Utils_String::strtoboolstr($val); - } - } - } - - if ($key == 'birth_date' && $val) { - CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key); - } - elseif ($key == 'deceased_date' && $val) { - CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key); - } - elseif ($key == 'is_deceased' && $val) { - $params[$key] = CRM_Utils_String::strtoboolstr($val); - } - elseif ($key == 'gender') { - //CRM-4360 - $params[$key] = $this->checkGender($val); - } - } - - //now format custom data. - foreach ($params as $key => $field) { - if (is_array($field)) { - $isAddressCustomField = FALSE; - foreach ($field as $value) { - $break = FALSE; - if (is_array($value)) { - foreach ($value as $name => $testForEmpty) { - if ($addressCustomFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) { - $isAddressCustomField = TRUE; - break; - } - // check if $value does not contain IM provider or phoneType - if (($name !== 'phone_type_id' || $name !== 'provider_id') && ($testForEmpty === '' || $testForEmpty == NULL)) { - $break = TRUE; - break; - } - } - } - else { - $break = TRUE; - } - - if (!$break) { - require_once 'CRM/Utils/DeprecatedUtils.php'; - _civicrm_api3_deprecated_add_formatted_param($value, $formatted); - } - } - if (!$isAddressCustomField) { - continue; - } - } - - $formatValues = array( - $key => $field, - ); - - if (($key !== 'preferred_communication_method') && (array_key_exists($key, $contactFields))) { - // due to merging of individual table and - // contact table, we need to avoid - // preferred_communication_method forcefully - $formatValues['contact_type'] = $formatted['contact_type']; - } - - if ($key == 'id' && isset($field)) { - $formatted[$key] = $field; - } - require_once 'CRM/Utils/DeprecatedUtils.php'; - _civicrm_api3_deprecated_add_formatted_param($formatValues, $formatted); - - //Handling Custom Data - // note: Address custom fields will be handled separately inside _civicrm_api3_deprecated_add_formatted_param - if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && - array_key_exists($customFieldID, $customFields) && - !array_key_exists($customFieldID, $addressCustomFields) - ) { - - $extends = CRM_Utils_Array::value('extends', $customFields[$customFieldID]); - $htmlType = CRM_Utils_Array::value('html_type', $customFields[$customFieldID]); - switch ($htmlType) { - case 'Select': - case 'Radio': - case 'Autocomplete-Select': - if ($customFields[$customFieldID]['data_type'] == 'String' || $customFields[$customFieldID]['data_type'] == 'Int') { - $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); - foreach ($customOption as $customValue) { - $val = CRM_Utils_Array::value('value', $customValue); - $label = CRM_Utils_Array::value('label', $customValue); - $label = strtolower($label); - $value = strtolower(trim($formatted[$key])); - if (($value == $label) || ($value == strtolower($val))) { - $params[$key] = $formatted[$key] = $val; - } - } - } - break; - - case 'CheckBox': - case 'AdvMulti-Select': - case 'Multi-Select': - - if (!empty($formatted[$key]) && !empty($params[$key])) { - $mulValues = explode(',', $formatted[$key]); - $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); - $formatted[$key] = array(); - $params[$key] = array(); - foreach ($mulValues as $v1) { - foreach ($customOption as $v2) { - if ((strtolower($v2['label']) == strtolower(trim($v1))) || - (strtolower($v2['value']) == strtolower(trim($v1))) - ) { - if ($htmlType == 'CheckBox') { - $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1; - } - else { - $params[$key][] = $formatted[$key][] = $v2['value']; - } - } - } - } - } - break; - } - } - } - - if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && array_key_exists($customFieldID, $customFields) && - !array_key_exists($customFieldID, $addressCustomFields) - ) { - // @todo calling api functions directly is not supported - _civicrm_api3_custom_format_params($params, $formatted, $extends); - } - - // to check if not update mode and unset the fields with empty value. - if (!$this->_updateWithId && array_key_exists('custom', $formatted)) { - foreach ($formatted['custom'] as $customKey => $customvalue) { - if (empty($formatted['custom'][$customKey][-1]['is_required'])) { - $formatted['custom'][$customKey][-1]['is_required'] = $customFields[$customKey]['is_required']; - } - $emptyValue = CRM_Utils_Array::value('value', $customvalue[-1]); - if (!isset($emptyValue)) { - unset($formatted['custom'][$customKey]); - } - } - } - - // parse street address, CRM-5450 - if ($this->_parseStreetAddress) { - if (array_key_exists('address', $formatted) && is_array($formatted['address'])) { - foreach ($formatted['address'] as $instance => & $address) { - $streetAddress = CRM_Utils_Array::value('street_address', $address); - if (empty($streetAddress)) { - continue; - } - // parse address field. - $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($streetAddress); - - //street address consider to be parsed properly, - //If we get street_name and street_number. - if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) { - $parsedFields = array_fill_keys(array_keys($parsedFields), ''); - } - - // merge parse address w/ main address block. - $address = array_merge($address, $parsedFields); - } - } - } - } - /** * Generate status and error message for unparsed street address records. * @@ -2090,9 +1882,9 @@ public function formatCommonData($params, &$formatted, &$contactFields) { */ public function processMessage(&$values, $statusFieldName, $returnCode) { if (empty($this->_unparsedStreetAddressContacts)) { - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'IMPORTED', - ); + ]; } else { $errorMessage = ts("Record imported successfully but unable to parse the street address: "); @@ -2101,10 +1893,10 @@ public function processMessage(&$values, $statusFieldName, $returnCode) { $errorMessage .= "\n Contact ID:" . $contactValue['id'] . " " . $contactValue['streetAddress'] . ""; } array_unshift($values, $errorMessage); - $importRecordParams = array( + $importRecordParams = [ $statusFieldName => 'ERROR', "${statusFieldName}Msg" => $errorMessage, - ); + ]; $returnCode = CRM_Import_Parser::UNPARSED_ADDRESS_WARNING; } $this->updateImportRecord($values[count($values) - 1], $importRecordParams); @@ -2122,7 +1914,7 @@ public function checkRelatedContactFields($relKey, $params) { $allowToCreate = FALSE; //build the mapper field array. - static $relatedContactFields = array(); + static $relatedContactFields = []; if (!isset($relatedContactFields[$relKey])) { foreach ($this->_mapperRelated as $key => $name) { if (!$name) { @@ -2130,7 +1922,7 @@ public function checkRelatedContactFields($relKey, $params) { } if (!empty($relatedContactFields[$name]) && !is_array($relatedContactFields[$name])) { - $relatedContactFields[$name] = array(); + $relatedContactFields[$name] = []; } $fldName = CRM_Utils_Array::value($key, $this->_mapperRelatedContactDetails); if ($fldName == 'url') { @@ -2162,7 +1954,7 @@ public function checkRelatedContactFields($relKey, $params) { * @return array $subTypes */ public static function getSubtypes($contactType) { - $subTypes = array(); + $subTypes = []; $types = CRM_Contact_BAO_ContactType::subTypeInfo($contactType); if (count($types) > 0) { @@ -2193,15 +1985,27 @@ protected function getPossibleContactMatches($params) { $extIDMatch = NULL; if (!empty($params['external_identifier'])) { - $extIDContact = civicrm_api3('Contact', 'get', array( + // Check for any match on external id, deleted or otherwise. + $extIDContact = civicrm_api3('Contact', 'get', [ 'external_identifier' => $params['external_identifier'], - 'return' => 'id', - )); + 'showAll' => 'all', + 'return' => ['id', 'contact_is_deleted'], + ]); if (isset($extIDContact['id'])) { $extIDMatch = $extIDContact['id']; + + if ($extIDContact['values'][$extIDMatch]['contact_is_deleted'] == 1) { + // If the contact is deleted, update external identifier to be blank + // to avoid key error from MySQL. + $params = ['id' => $extIDMatch, 'external_identifier' => '']; + civicrm_api3('Contact', 'create', $params); + + // And now it is no longer a match. + $extIDMatch = NULL; + } } } - $checkParams = array('check_permissions' => FALSE, 'match' => $params); + $checkParams = ['check_permissions' => FALSE, 'match' => $params]; $checkParams['match']['contact_type'] = $this->_contactType; $possibleMatches = civicrm_api3('Contact', 'duplicatecheck', $checkParams); @@ -2210,14 +2014,52 @@ protected function getPossibleContactMatches($params) { } if ($possibleMatches['count']) { if (in_array($extIDMatch, array_keys($possibleMatches['values']))) { - return array($extIDMatch); + return [$extIDMatch]; } else { throw new CRM_Core_Exception(ts( 'Matching this contact based on the de-dupe rule would cause an external ID conflict')); } } - return array($extIDMatch); + return [$extIDMatch]; + } + + /** + * Format the form mapping parameters ready for the parser. + * + * @param int $count + * Number of rows. + * + * @return array $parserParameters + */ + public static function getParameterForParser($count) { + $baseArray = []; + for ($i = 0; $i < $count; $i++) { + $baseArray[$i] = NULL; + } + $parserParameters['mapperLocType'] = $baseArray; + $parserParameters['mapperPhoneType'] = $baseArray; + $parserParameters['mapperImProvider'] = $baseArray; + $parserParameters['mapperWebsiteType'] = $baseArray; + $parserParameters['mapperRelated'] = $baseArray; + $parserParameters['relatedContactType'] = $baseArray; + $parserParameters['relatedContactDetails'] = $baseArray; + $parserParameters['relatedContactLocType'] = $baseArray; + $parserParameters['relatedContactPhoneType'] = $baseArray; + $parserParameters['relatedContactImProvider'] = $baseArray; + $parserParameters['relatedContactWebsiteType'] = $baseArray; + + return $parserParameters; + + } + + /** + * Set field metadata. + */ + protected function setFieldMetadata() { + $this->setImportableFieldsMetadata($this->getContactImportMetadata()); + // Probably no longer needed but here for now. + $this->_relationships = $this->getRelationships(); } } diff --git a/CRM/Contact/Page/AJAX.php b/CRM/Contact/Page/AJAX.php index cfdff39d0f86..3ac0326cf743 100644 --- a/CRM/Contact/Page/AJAX.php +++ b/CRM/Contact/Page/AJAX.php @@ -1,9 +1,9 @@ $cfID); - $returnProperties = array('filter', 'data_type', 'is_active'); - $cf = array(); + $params = ['id' => $cfID]; + $returnProperties = ['filter', 'data_type', 'is_active']; + $cf = []; CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomField', $params, $cf, $returnProperties); if (!$cf['id'] || !$cf['is_active'] || $cf['data_type'] != 'ContactReference') { - CRM_Utils_System::civiExit('error'); + CRM_Utils_System::civiExit(1); } if (!empty($cf['filter'])) { - $filterParams = array(); + $filterParams = []; parse_str($cf['filter'], $filterParams); $action = CRM_Utils_Array::value('action', $filterParams); - if (!empty($action) && !in_array($action, array('get', 'lookup'))) { - CRM_Utils_System::civiExit('error'); + if (!empty($action) && !in_array($action, ['get', 'lookup'])) { + CRM_Utils_System::civiExit(1); } if (!empty($filterParams['group'])) { @@ -82,17 +84,17 @@ public static function contactReference() { 'contact_reference_options' ), '1'); - $return = array_unique(array_merge(array('sort_name'), $list)); + $return = array_unique(array_merge(['sort_name'], $list)); $limit = Civi::settings()->get('search_autocomplete_count'); - $params = array('offset' => 0, 'rowCount' => $limit, 'version' => 3); + $params = ['offset' => 0, 'rowCount' => $limit, 'version' => 3]; foreach ($return as $fld) { $params["return.{$fld}"] = 1; } if (!empty($action)) { - $excludeGet = array( + $excludeGet = [ 'reset', 'key', 'className', @@ -106,7 +108,7 @@ public static function contactReference() { 's', 'q', 'action', - ); + ]; foreach ($_GET as $param => $val) { if (empty($val) || in_array($param, $excludeGet) || @@ -136,18 +138,18 @@ public static function contactReference() { $contact = civicrm_api('Contact', 'Get', $params); if (!empty($contact['is_error'])) { - CRM_Utils_System::civiExit('error'); + CRM_Utils_System::civiExit(1); } - $contactList = array(); + $contactList = []; foreach ($contact['values'] as $value) { - $view = array(); + $view = []; foreach ($return as $fld) { if (!empty($value[$fld])) { $view[] = $value[$fld]; } } - $contactList[] = array('id' => $value['id'], 'text' => implode(' :: ', $view)); + $contactList[] = ['id' => $value['id'], 'text' => implode(' :: ', $view)]; } if (!empty($_GET['is_unit_test'])) { @@ -211,13 +213,13 @@ public static function getPCPList() { "; $dao = CRM_Core_DAO::executeQuery($query); - $output = array('results' => array(), 'more' => FALSE); + $output = ['results' => [], 'more' => FALSE]; while ($dao->fetch()) { if (++$count > $max) { $output['more'] = TRUE; } else { - $output['results'][] = array('id' => $dao->id, 'text' => $dao->data); + $output['results'][] = ['id' => $dao->id, 'text' => $dao->data]; } } CRM_Utils_JSON::output($output); @@ -234,7 +236,7 @@ public static function relationship() { CRM_Utils_System::permissionDenied(); } - $ret = array('is_error' => 0); + $ret = ['is_error' => 0]; list($relTypeId, $b, $a) = explode('_', $relType); @@ -247,13 +249,22 @@ public static function relationship() { // Loop through multiple case clients foreach ($clientList as $i => $sourceContactID) { try { - $result = civicrm_api3('relationship', 'create', array( + $params = [ 'case_id' => $caseID, 'relationship_type_id' => $relTypeId, "contact_id_$a" => $relContactID, "contact_id_$b" => $sourceContactID, + 'sequential' => TRUE, + ]; + // first check if there is any existing relationship present with same parameters. + // If yes then update the relationship by setting active and start date to current time + $relationship = civicrm_api3('Relationship', 'get', $params)['values']; + $params = array_merge(CRM_Utils_Array::value(0, $relationship, $params), [ 'start_date' => 'now', - )); + 'is_active' => TRUE, + 'end_date' => '', + ]); + $result = civicrm_api3('relationship', 'create', $params); } catch (CiviCRM_API3_Exception $e) { $ret['is_error'] = 1; @@ -261,9 +272,12 @@ public static function relationship() { } // Save activity only for the primary (first) client if ($i == 0 && empty($result['is_error'])) { - CRM_Case_BAO_Case::createCaseRoleActivity($caseID, $result['id'], $relContactID); + CRM_Case_BAO_Case::createCaseRoleActivity($caseID, $result['id'], $relContactID, $sourceContactID); } } + if (!empty($_REQUEST['is_unit_test'])) { + return $ret; + } CRM_Utils_JSON::output($ret); } @@ -273,9 +287,9 @@ public static function relationship() { */ public static function customField() { $fieldId = CRM_Utils_Type::escape($_REQUEST['id'], 'Integer'); - $params = array('id' => $fieldId); - $returnProperties = array('help_pre', 'help_post'); - $values = array(); + $params = ['id' => $fieldId]; + $returnProperties = ['help_pre', 'help_post']; + $values = []; CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomField', $params, $values, $returnProperties); CRM_Utils_JSON::output($values); @@ -308,8 +322,8 @@ public static function deleteCustomValue() { /** * check the CMS username. */ - static public function checkUserName() { - $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), array('for', 'ts')); + public static function checkUserName() { + $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), ['for', 'ts']); $sig = CRM_Utils_Request::retrieve('sig', 'String'); $for = CRM_Utils_Request::retrieve('for', 'String'); if ( @@ -317,26 +331,26 @@ static public function checkUserName() { || $for != 'civicrm/ajax/cmsuser' || !$signer->validate($sig, $_REQUEST) ) { - $user = array('name' => 'error'); + $user = ['name' => 'error']; CRM_Utils_JSON::output($user); } $config = CRM_Core_Config::singleton(); $username = trim(CRM_Utils_Array::value('cms_name', $_REQUEST)); - $params = array('name' => $username); + $params = ['name' => $username]; - $errors = array(); + $errors = []; $config->userSystem->checkUserNameEmailExists($params, $errors); if (isset($errors['cms_name']) || isset($errors['name'])) { //user name is not available - $user = array('name' => 'no'); + $user = ['name' => 'no']; CRM_Utils_JSON::output($user); } else { //user name is available - $user = array('name' => 'yes'); + $user = ['name' => 'yes']; CRM_Utils_JSON::output($user); } @@ -388,6 +402,7 @@ public static function getContactEmail() { } if ($queryString) { + $result = []; $offset = CRM_Utils_Array::value('offset', $_GET, 0); $rowCount = Civi::settings()->get('search_autocomplete_count'); @@ -411,16 +426,16 @@ public static function getContactEmail() { // send query to hook to be modified if needed CRM_Utils_Hook::contactListQuery($query, $name, - CRM_Utils_Request::retrieve('context', 'String'), + CRM_Utils_Request::retrieve('context', 'Alphanumeric'), CRM_Utils_Request::retrieve('cid', 'Positive') ); $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $result[] = array( + $result[] = [ 'id' => $dao->id, 'text' => $dao->name, - ); + ]; } } else { @@ -436,7 +451,7 @@ public static function getContactEmail() { // send query to hook to be modified if needed CRM_Utils_Hook::contactListQuery($query, $name, - CRM_Utils_Request::retrieve('context', 'String'), + CRM_Utils_Request::retrieve('context', 'Alphanumeric'), CRM_Utils_Request::retrieve('cid', 'Positive') ); @@ -444,15 +459,13 @@ public static function getContactEmail() { while ($dao->fetch()) { //working here - $result[] = array( + $result[] = [ 'text' => '"' . $dao->name . '" <' . $dao->email . '>', 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->email}" : '"' . $dao->name . '" <' . $dao->email . '>', - ); + ]; } } - if ($result) { - CRM_Utils_JSON::output($result); - } + CRM_Utils_JSON::output($result); } } CRM_Utils_System::civiExit(); @@ -461,33 +474,28 @@ public static function getContactEmail() { public static function getContactPhone() { $queryString = NULL; + $sqlParmas = []; //check for mobile type $phoneTypes = CRM_Core_OptionGroup::values('phone_type', TRUE, FALSE, FALSE, NULL, 'name'); $mobileType = CRM_Utils_Array::value('Mobile', $phoneTypes); - $name = CRM_Utils_Array::value('name', $_GET); + $name = CRM_Utils_Request::retrieveValue('name', 'String', NULL, FALSE, 'GET'); if ($name) { - $name = CRM_Utils_Type::escape($name, 'String'); - $queryString = " ( cc.sort_name LIKE '%$name%' OR cp.phone LIKE '%$name%' ) "; + $key = (int) count(array_keys($sqlParmas)) + 1; + $queryString = " ( cc.sort_name LIKE %{$key} OR cp.phone LIKE %{$key} ) "; + $sqlParams[$key] = ['%' . $name . '%', 'String']; } else { - $cid = CRM_Utils_Array::value('cid', $_GET); + $cid = CRM_Utils_Request::retrieveValue('cid', 'CommaSeparatedIntegers', NULL, FALSE, 'GET'); if ($cid) { - //check cid for integer - $contIDS = explode(',', $cid); - foreach ($contIDS as $contID) { - CRM_Utils_Type::escape($contID, 'Integer'); - } $queryString = " cc.id IN ( $cid )"; } } if ($queryString) { - $offset = CRM_Utils_Array::value('offset', $_GET, 0); - $rowCount = CRM_Utils_Array::value('rowcount', $_GET, 20); - - $offset = CRM_Utils_Type::escape($offset, 'Int'); - $rowCount = CRM_Utils_Type::escape($rowCount, 'Int'); + $result = []; + $offset = (int) CRM_Utils_Request::retrieveValue('offset', 'Integer', 0, FALSE, 'GET'); + $rowCount = (int) CRM_Utils_Request::retrieveValue('rowcount', 'Integer', 20, FALSE, 'GET'); // add acl clause here list($aclFrom, $aclWhere) = CRM_Contact_BAO_Contact_Permission::cacheClause('cc'); @@ -507,27 +515,23 @@ public static function getContactPhone() { // send query to hook to be modified if needed CRM_Utils_Hook::contactListQuery($query, $name, - CRM_Utils_Request::retrieve('context', 'String'), + CRM_Utils_Request::retrieve('context', 'Alphanumeric'), CRM_Utils_Request::retrieve('cid', 'Positive') ); - $dao = CRM_Core_DAO::executeQuery($query); + $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); while ($dao->fetch()) { - $result[] = array( + $result[] = [ 'text' => '"' . $dao->name . '" (' . $dao->phone . ')', 'id' => (CRM_Utils_Array::value('id', $_GET)) ? "{$dao->id}::{$dao->phone}" : '"' . $dao->name . '" <' . $dao->phone . '>', - ); + ]; } - } - - if ($result) { CRM_Utils_JSON::output($result); } CRM_Utils_System::civiExit(); } - public static function buildSubTypes() { $parent = CRM_Utils_Request::retrieve('parentId', 'Positive'); @@ -597,12 +601,12 @@ public static function getSignature() { $query = "SELECT signature_text, signature_html FROM civicrm_email WHERE id = {$emailID}"; $dao = CRM_Core_DAO::executeQuery($query); - $signatures = array(); + $signatures = []; while ($dao->fetch()) { - $signatures = array( + $signatures = [ 'signature_text' => $dao->signature_text, 'signature_html' => $dao->signature_html, - ); + ]; } CRM_Utils_JSON::output($signatures); @@ -620,24 +624,9 @@ public static function processDupes() { return; } - $exception = new CRM_Dedupe_DAO_Exception(); - $exception->contact_id1 = $cid; - $exception->contact_id2 = $oid; - //make sure contact2 > contact1. - if ($cid > $oid) { - $exception->contact_id1 = $oid; - $exception->contact_id2 = $cid; - } - $exception->find(TRUE); - $status = NULL; - if ($oper == 'dupe-nondupe') { - $status = $exception->save(); - } - if ($oper == 'nondupe-dupe') { - $status = $exception->delete(); - } + $status = self::markNonDuplicates($cid, $oid, $oper); - CRM_Utils_JSON::output(array('status' => ($status) ? $oper : $status)); + CRM_Utils_JSON::output(['status' => ($status) ? $oper : $status]); } /** @@ -649,25 +638,29 @@ public static function getDedupes() { $gid = CRM_Utils_Request::retrieve('gid', 'Positive'); $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive'); - $selected = isset($_REQUEST['selected']) ? CRM_Utils_Type::escape($_REQUEST['selected'], 'Integer') : 0; + $limit = CRM_Utils_Request::retrieveValue('limit', 'Positive', 0); + $null = NULL; + $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $null, FALSE, '{}'); + $selected = CRM_Utils_Request::retrieveValue('selected', 'Boolean'); if ($rowCount < 0) { $rowCount = 0; } $whereClause = $orderByClause = ''; - $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); - $searchRows = array(); + $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, json_decode($criteria, TRUE), TRUE, $limit); + + $searchRows = []; $searchParams = self::getSearchOptionsFromRequest(); - $queryParams = array(); + $queryParams = []; $join = ''; - $where = array(); + $where = []; $isOrQuery = self::isOrQuery(); $nextParamKey = 3; - $mappings = array( + $mappings = [ 'dst' => 'cc1.display_name', 'src' => 'cc2.display_name', 'dst_email' => 'ce1.email', @@ -676,24 +669,24 @@ public static function getDedupes() { 'src_postcode' => 'ca2.postal_code', 'dst_street' => 'ca1.street', 'src_street' => 'ca2.street', - ); + ]; foreach ($mappings as $key => $dbName) { if (!empty($searchParams[$key])) { // CRM-18694. $wildcard = strstr($key, 'postcode') ? '' : '%'; - $queryParams[$nextParamKey] = array($wildcard . $searchParams[$key] . '%', 'String'); + $queryParams[$nextParamKey] = [$wildcard . $searchParams[$key] . '%', 'String']; $where[] = $dbName . " LIKE %{$nextParamKey} "; $nextParamKey++; } } if ($isOrQuery) { - $whereClause = ' ( ' . implode(' OR ', $where) . ' ) '; + $whereClause = ' ( ' . implode(' OR ', $where) . ' ) '; } else { if (!empty($where)) { - $whereClause = implode(' AND ', $where); + $whereClause = implode(' AND ', $where); } } $whereClause .= $whereClause ? ' AND de.id IS NULL' : ' de.id IS NULL'; @@ -703,7 +696,7 @@ public static function getDedupes() { } $join .= CRM_Dedupe_Merger::getJoinOnDedupeTable(); - $select = array( + $select = [ 'cc1.contact_type' => 'dst_contact_type', 'cc1.display_name' => 'dst_display_name', 'cc1.contact_sub_type' => 'dst_contact_sub_type', @@ -716,7 +709,7 @@ public static function getDedupes() { 'ca2.postal_code' => 'src_postcode', 'ca1.street_address' => 'dst_street', 'ca2.street_address' => 'src_street', - ); + ]; if ($select) { $join .= " INNER JOIN civicrm_contact cc1 ON cc1.id = pn.entity_id1"; @@ -811,9 +804,17 @@ public static function getDedupes() { $searchRows[$count]['weight'] = CRM_Utils_Array::value('weight', $pair); if (!empty($pairInfo['data']['canMerge'])) { - $mergeParams = "reset=1&cid={$pairInfo['entity_id1']}&oid={$pairInfo['entity_id2']}&action=update&rgid={$rgid}&limit=" . CRM_Utils_Request::retrieve('limit', 'Integer'); + $mergeParams = [ + 'reset' => 1, + 'cid' => $pairInfo['entity_id1'], + 'oid' => $pairInfo['entity_id2'], + 'action' => 'update', + 'rgid' => $rgid, + 'criteria' => $criteria, + 'limit' => CRM_Utils_Request::retrieve('limit', 'Integer'), + ]; if ($gid) { - $mergeParams .= "&gid={$gid}"; + $mergeParams['gid'] = $gid; } $searchRows[$count]['actions'] = "" . ts('flip') . " | "; @@ -826,11 +827,11 @@ public static function getDedupes() { $count++; } - $dupePairs = array( + $dupePairs = [ 'data' => $searchRows, 'recordsTotal' => $iTotal, 'recordsFiltered' => $iFilteredTotal, - ); + ]; if (!empty($_REQUEST['is_unit_test'])) { return $dupePairs; } @@ -843,10 +844,10 @@ public static function getDedupes() { * @return array */ public static function getSearchOptionsFromRequest() { - $searchParams = array(); + $searchParams = []; $searchData = CRM_Utils_Array::value('search', $_REQUEST); $searchData['value'] = CRM_Utils_Type::escape($searchData['value'], 'String'); - $selectorElements = array( + $selectorElements = [ 'is_selected', 'is_selected_input', 'src_image', @@ -862,7 +863,7 @@ public static function getSearchOptionsFromRequest() { 'conflicts', 'weight', 'actions', - ); + ]; $columns = $_REQUEST['columns']; foreach ($columns as $column) { @@ -893,6 +894,48 @@ public static function isOrQuery() { return !empty($searchData['value']); } + /** + * Mark not duplicates. + * + * Note this function would sensibly be replaced by an api-call but extracting here to add a test first. + * + * I would have like to make it private but test class accesses it & it doesn't warrant being a BAO class + * as it should feel very endangered. + * + * @param int $cid + * @param int $oid + * @param "dupe-nondupe|nondupe-dupe" $oper + * + * @return \CRM_Core_DAO|mixed|null + */ + public static function markNonDuplicates($cid, $oid, $oper) { + if ($oper == 'dupe-nondupe') { + try { + civicrm_api3('Exception', 'create', ['contact_id1' => $cid, 'contact_id2' => $oid]); + return TRUE; + } + catch (CiviCRM_API3_Exception $e) { + return FALSE; + } + } + + $exception = new CRM_Dedupe_DAO_Exception(); + $exception->contact_id1 = $cid; + $exception->contact_id2 = $oid; + //make sure contact2 > contact1. + if ($cid > $oid) { + $exception->contact_id1 = $oid; + $exception->contact_id2 = $cid; + } + $exception->find(TRUE); + $status = NULL; + + if ($oper == 'nondupe-dupe') { + $status = $exception->delete(); + } + return $status; + } + /** * Retrieve a PDF Page Format for the PDF Letter form. */ @@ -923,7 +966,10 @@ public static function paperSize() { public static function flipDupePairs($prevNextId = NULL) { if (!$prevNextId) { // @todo figure out if this is always POST & specify that rather than inexact GET - $prevNextId = CRM_Utils_Request::retrieve('pnid', 'Integer'); + + // We cannot use CRM_Utils_Request::retrieve() because it might be an array. + // It later gets validated in escapeAll below. + $prevNextId = $_REQUEST['pnid']; } $onlySelected = FALSE; @@ -955,22 +1001,22 @@ public static function selectUnselectContacts() { $elements[$key] = self::_convertToId($element); } CRM_Utils_Type::escapeAll($elements, 'Integer'); - CRM_Core_BAO_PrevNextCache::markSelection($cacheKey, $actionToPerform, $elements); + Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform, $elements); } else { - CRM_Core_BAO_PrevNextCache::markSelection($cacheKey, $actionToPerform); + Civi::service('prevnext')->markSelection($cacheKey, $actionToPerform); } } elseif ($variableType == 'single') { $cId = self::_convertToId($name); CRM_Utils_Type::escape($cId, 'Integer'); $action = ($state == 'checked') ? 'select' : 'unselect'; - CRM_Core_BAO_PrevNextCache::markSelection($cacheKey, $action, $cId); + Civi::service('prevnext')->markSelection($cacheKey, $action, $cId); } - $contactIds = CRM_Core_BAO_PrevNextCache::getSelection($cacheKey); + $contactIds = Civi::service('prevnext')->getSelection($cacheKey); $countSelectionCids = count($contactIds[$cacheKey]); - $arrRet = array('getCount' => $countSelectionCids); + $arrRet = ['getCount' => $countSelectionCids]; CRM_Utils_JSON::output($arrRet); } @@ -992,10 +1038,10 @@ public static function getAddressDisplay() { $addressVal["error_message"] = "no contact id found"; } else { - $entityBlock = array( + $entityBlock = [ 'contact_id' => $contactId, 'entity_id' => $contactId, - ); + ]; $addressVal = CRM_Core_BAO_Address::getValues($entityBlock); } @@ -1006,17 +1052,15 @@ public static function getAddressDisplay() { * Mark dupe pairs as selected from un-selected state or vice-versa, in dupe cache table. */ public static function toggleDedupeSelect() { - $rgid = CRM_Utils_Type::escape($_REQUEST['rgid'], 'Integer'); - $gid = CRM_Utils_Type::escape($_REQUEST['gid'], 'Integer'); $pnid = $_REQUEST['pnid']; $isSelected = CRM_Utils_Type::escape($_REQUEST['is_selected'], 'Boolean'); + $cacheKeyString = CRM_Utils_Request::retrieve('cacheKey', 'Alphanumeric', $null, FALSE); - $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); - - $params = array( - 1 => array($isSelected, 'Boolean'), - 3 => array("$cacheKeyString%", 'String'), // using % to address rows with conflicts as well - ); + $params = [ + 1 => [$isSelected, 'Boolean'], + // using % to address rows with conflicts as well + 3 => ["$cacheKeyString%", 'String'], + ]; //check pnid is_array or integer $whereClause = NULL; @@ -1028,10 +1072,10 @@ public static function toggleDedupeSelect() { else { $pnid = CRM_Utils_Type::escape($pnid, 'Integer'); $whereClause = " id = %2"; - $params[2] = array($pnid, 'Integer'); + $params[2] = [$pnid, 'Integer']; } - $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cacheKey LIKE %3"; + $sql = "UPDATE civicrm_prevnext_cache SET is_selected = %1 WHERE {$whereClause} AND cachekey LIKE %3"; CRM_Core_DAO::executeQuery($sql, $params); CRM_Utils_System::civiExit(); @@ -1042,7 +1086,7 @@ public static function toggleDedupeSelect() { */ public static function getContactRelationships() { $contactID = CRM_Utils_Type::escape($_GET['cid'], 'Integer'); - $context = CRM_Utils_Type::escape($_GET['context'], 'String'); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric'); $relationship_type_id = CRM_Utils_Type::escape(CRM_Utils_Array::value('relationship_type_id', $_GET), 'Integer', FALSE); if (!CRM_Contact_BAO_Contact_Permission::allow($contactID)) { diff --git a/CRM/Contact/Page/CustomSearch.php b/CRM/Contact/Page/CustomSearch.php index ec3f8afd4f49..0ee1969f915b 100644 --- a/CRM/Contact/Page/CustomSearch.php +++ b/CRM/Contact/Page/CustomSearch.php @@ -1,9 +1,9 @@ fetch()) { if (trim($dao->description)) { $rows[$dao->value] = $dao->description; diff --git a/CRM/Contact/Page/DashBoard.php b/CRM/Contact/Page/DashBoard.php index 69e792b62179..3047958b975a 100644 --- a/CRM/Contact/Page/DashBoard.php +++ b/CRM/Contact/Page/DashBoard.php @@ -1,9 +1,9 @@ $item['label'], 'is_reserved' => $allDashlets[$item['dashboard_id']]['is_reserved'], - ); + ]; unset($allDashlets[$item['dashboard_id']]); } foreach ($allDashlets as $dashletID => $values) { $key = "{$dashletID}-0"; - $availableDashlets[$key] = array( + $availableDashlets[$key] = [ 'label' => $values['label'], 'is_reserved' => $values['is_reserved'], - ); + ]; } $this->assign('contactDashlets', $contactDashlets); diff --git a/CRM/Contact/Page/DedupeException.php b/CRM/Contact/Page/DedupeException.php index 48da4951797f..48452d215d6f 100644 --- a/CRM/Contact/Page/DedupeException.php +++ b/CRM/Contact/Page/DedupeException.php @@ -1,9 +1,9 @@ find(); - $contactIds = array(); - while ($exception->fetch()) { - $key = "{$exception->contact_id1}_{$exception->contact_id2}"; - $contactIds[$exception->contact_id1] = $exception->contact_id1; - $contactIds[$exception->contact_id2] = $exception->contact_id2; - $dedupeExceptions[$key] = array( - 'main' => array('id' => $exception->contact_id1), - 'other' => array('id' => $exception->contact_id2), - ); - } - //get the dupe contacts display names. - if (!empty($dedupeExceptions)) { - $sql = 'select id, display_name from civicrm_contact where id IN ( ' . implode(', ', $contactIds) . ' )'; - $contact = CRM_Core_DAO::executeQuery($sql); - $displayNames = array(); - while ($contact->fetch()) { - $displayNames[$contact->id] = $contact->display_name; - } - foreach ($dedupeExceptions as $key => & $values) { - $values['main']['name'] = CRM_Utils_Array::value($values['main']['id'], $displayNames); - $values['other']['name'] = CRM_Utils_Array::value($values['other']['id'], $displayNames); - } + public function run() { + $this->initializePager(); + $this->assign('exceptions', $this->getExceptions()); + return parent::run(); + } + + /** + * Method to initialize pager + * + * @access protected + */ + protected function initializePager() { + $params = []; + + $contactOneQ = CRM_Utils_Request::retrieve('crmContact1Q', 'String'); + + if ($contactOneQ) { + $params['contact_id1.display_name'] = ['LIKE' => '%' . $contactOneQ . '%']; + $params['contact_id2.display_name'] = ['LIKE' => '%' . $contactOneQ . '%']; + + $params['options']['or'] = [["contact_id1.display_name", "contact_id2.display_name"]]; } - $this->assign('dedupeExceptions', $dedupeExceptions); + + $totalitems = civicrm_api3('Exception', "getcount", $params); + $params = [ + 'total' => $totalitems, + 'rowCount' => CRM_Utils_Pager::ROWCOUNT, + 'status' => ts('Dedupe Exceptions %%StatusMessage%%'), + 'buttonBottom' => 'PagerBottomButton', + 'buttonTop' => 'PagerTopButton', + 'pageID' => $this->get(CRM_Utils_Pager::PAGE_ID), + ]; + $this->_pager = new CRM_Utils_Pager($params); + $this->assign_by_ref('pager', $this->_pager); } /** - * the main function that is called when the page loads, - * it decides the which action has to be taken for the page. + * Function to get the exceptions * - * @return null + * @return array $exceptionsd */ - public function run() { - $this->preProcess(); - return parent::run(); + public function getExceptions() { + list($offset, $limit) = $this->_pager->getOffsetAndRowCount(); + $contactOneQ = CRM_Utils_Request::retrieve('crmContact1Q', 'String'); + + if (!$contactOneQ) { + $contactOneQ = ''; + } + + $this->assign('searchcontact1', $contactOneQ); + + $params = [ + "options" => ['limit' => $limit, 'offset' => $offset], + 'return' => ["contact_id1.display_name", "contact_id2.display_name", "contact_id1", "contact_id2"], + ]; + + if ($contactOneQ != '') { + $params['contact_id1.display_name'] = ['LIKE' => '%' . $contactOneQ . '%']; + $params['contact_id2.display_name'] = ['LIKE' => '%' . $contactOneQ . '%']; + + $params['options']['or'] = [["contact_id1.display_name", "contact_id2.display_name"]]; + } + + $exceptions = civicrm_api3("Exception", "get", $params); + $exceptions = $exceptions["values"]; + return $exceptions; } } diff --git a/CRM/Contact/Page/DedupeFind.php b/CRM/Contact/Page/DedupeFind.php index 012529f882ed..b7a22ea66611 100644 --- a/CRM/Contact/Page/DedupeFind.php +++ b/CRM/Contact/Page/DedupeFind.php @@ -1,9 +1,9 @@ selected === NULL) ? NULL : (int) $this->selected; + } /** * Get BAO Name. @@ -53,22 +70,49 @@ public function getBAOName() { public function &links() { } + /** + * Initialize properties from input. + */ + protected function initialize() { + $this->selected = CRM_Utils_Request::retrieveValue('selected', 'Boolean'); + } + /** * Browse all rule groups. */ public function run() { + $this->initialize(); $gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this, FALSE, 0); $action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 0); - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $limit = CRM_Utils_Request::retrieve('limit', 'Integer', $this); - $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive'); - $urlQry = array( + $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive', $this); + $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE, 0); + + $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $this, FALSE, '{}'); + $this->assign('criteria', $criteria); + + $isConflictMode = ($context == 'conflicts'); + if ($cid) { + $this->_cid = $cid; + } + if ($gid) { + $this->_gid = $gid; + } + $this->_rgid = $rgid; + + $urlQry = [ 'reset' => 1, 'rgid' => $rgid, 'gid' => $gid, - 'limit' => $limit, - ); + 'limit' => (int) $limit, + 'criteria' => $criteria, + ]; $this->assign('urlQuery', CRM_Utils_System::makeQueryString($urlQry)); + $this->assign('isSelected', $this->isSelected()); + $criteria = json_decode($criteria, TRUE); + $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, $criteria, TRUE, $limit); + $this->assign('cacheKey', $cacheKeyString); if ($context == 'search') { $context = 'search'; @@ -78,14 +122,14 @@ public function run() { if ($action & CRM_Core_Action::RENEW) { // empty cache if ($rgid) { - CRM_Core_BAO_PrevNextCache::deleteItem(NULL, CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid)); + CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKeyString); } $urlQry['action'] = 'update'; CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); } elseif ($action & CRM_Core_Action::MAP) { // do a batch merge if requested - $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', 75); + $result = CRM_Dedupe_Merger::batchMerge($rgid, $gid, 'safe', 75, 2, $criteria, TRUE, NULL, $limit); $skippedCount = CRM_Utils_Request::retrieve('skipped', 'Positive', $this, FALSE, 0); $skippedCount = $skippedCount + count($result['skipped']); @@ -95,12 +139,12 @@ public function run() { if (empty($result['merged']) && empty($result['skipped'])) { $message = ''; if ($mergedCount >= 1) { - $message = ts("%1 pairs of duplicates were merged", array(1 => $mergedCount)); + $message = ts("%1 pairs of duplicates were merged", [1 => $mergedCount]); } if ($skippedCount >= 1) { $message = $message ? "{$message} and " : ''; $message .= ts("%1 pairs of duplicates were skipped due to conflict", - array(1 => $skippedCount) + [1 => $skippedCount] ); } $message .= ts(" during the batch merge process with safe mode."); @@ -123,99 +167,51 @@ public function run() { if ($action & CRM_Core_Action::UPDATE || $action & CRM_Core_Action::BROWSE ) { - $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE, 0); $this->action = CRM_Core_Action::UPDATE; $urlQry['snippet'] = 4; - if ($context == 'conflicts') { - $urlQry['selected'] = 1; - } $this->assign('sourceUrl', CRM_Utils_System::url('civicrm/ajax/dedupefind', $urlQry, FALSE, NULL, FALSE)); - //reload from cache table - $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); - - $stats = CRM_Dedupe_Merger::getMergeStatsMsg($cacheKeyString); + $stats = CRM_Dedupe_Merger::getMergeStats($cacheKeyString); if ($stats) { - CRM_Core_Session::setStatus($stats); + $message = CRM_Dedupe_Merger::getMergeStatsMsg($stats); + $status = empty($stats['skipped']) ? 'success' : 'alert'; + CRM_Core_Session::setStatus($message, ts('Batch Complete'), $status, ['expires' => 0]); // reset so we not displaying same message again CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); } - $join = CRM_Dedupe_Merger::getJoinOnDedupeTable(); - $where = "de.id IS NULL"; - if ($context == 'conflicts') { - $where .= " AND pn.is_selected = 1"; - } - $this->_mainContacts = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, $join, $where); + + $this->_mainContacts = CRM_Dedupe_Merger::getDuplicatePairs($rgid, $gid, !$isConflictMode, 0, $this->isSelected(), $isConflictMode, $criteria, TRUE, $limit); + if (empty($this->_mainContacts)) { - if ($context == 'conflicts') { + if ($isConflictMode) { // if the current screen was intended to list only selected contacts, move back to full dupe list $urlQry['action'] = 'update'; unset($urlQry['snippet']); CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), $urlQry)); } - if ($gid) { - $foundDupes = $this->get("dedupe_dupes_$gid"); - if (!$foundDupes) { - $foundDupes = CRM_Dedupe_Finder::dupesInGroup($rgid, $gid, $limit); - } - $this->set("dedupe_dupes_$gid", $foundDupes); - } - else { - $foundDupes = $this->get('dedupe_dupes'); - if (!$foundDupes) { - $foundDupes = CRM_Dedupe_Finder::dupes($rgid, array(), TRUE, $limit); - } - $this->set('dedupe_dupes', $foundDupes); - } - if (!$foundDupes) { - $ruleGroup = new CRM_Dedupe_BAO_RuleGroup(); - $ruleGroup->id = $rgid; - $ruleGroup->find(TRUE); - - $session = CRM_Core_Session::singleton(); - $session->setStatus(ts('No possible duplicates were found using %1 rule.', array(1 => $ruleGroup->name)), ts('None Found'), 'info'); - $url = CRM_Utils_System::url('civicrm/contact/deduperules', 'reset=1'); - if ($context == 'search') { - $url = $session->readUserContext(); - } - CRM_Utils_System::redirect($url); - } - else { - $mainContacts = CRM_Dedupe_Finder::parseAndStoreDupePairs($foundDupes, $cacheKeyString); - - if ($cid) { - $this->_cid = $cid; - } - if ($gid) { - $this->_gid = $gid; - } - $this->_rgid = $rgid; - $this->_mainContacts = $mainContacts; - - $urlQry['action'] = 'update'; - if ($this->_cid) { - $urlQry['cid'] = $this->_cid; - CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/contact/deduperules', - $urlQry - )); - } - else { - CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind', - $urlQry - )); - } + $ruleGroupName = civicrm_api3('RuleGroup', 'getvalue', ['id' => $rgid, 'return' => 'name']); + CRM_Core_Session::singleton()->setStatus(ts('No possible duplicates were found using %1 rule.', [1 => $ruleGroupName]), ts('None Found'), 'info'); + $url = CRM_Utils_System::url('civicrm/contact/deduperules', 'reset=1'); + if ($context == 'search') { + $url = CRM_Core_Session::singleton()->readUserContext(); } + CRM_Utils_System::redirect($url); } else { - if ($cid) { - $this->_cid = $cid; + $urlQry['action'] = 'update'; + if ($this->_cid) { + $urlQry['cid'] = $this->_cid; + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/contact/deduperules', + $urlQry + )); } - if ($gid) { - $this->_gid = $gid; + else { + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/contact/dedupefind', + $urlQry + )); } - $this->_rgid = $rgid; } $this->assign('action', $this->action); diff --git a/CRM/Contact/Page/DedupeMerge.php b/CRM/Contact/Page/DedupeMerge.php index df91a146f4a8..348f65b1d8f8 100644 --- a/CRM/Contact/Page/DedupeMerge.php +++ b/CRM/Contact/Page/DedupeMerge.php @@ -1,9 +1,9 @@ runAllViaWeb(); } else { CRM_Core_Session::setStatus(ts('Nothing to merge.')); } - - // parent run return parent::run(); } @@ -55,38 +52,41 @@ public function run() { * Build a queue of tasks by dividing dupe pairs in batches. */ public static function getRunner() { - $rgid = CRM_Utils_Request::retrieve('rgid', 'Positive'); - $gid = CRM_Utils_Request::retrieve('gid', 'Positive'); - $limit = CRM_Utils_Request::retrieve('limit', 'Positive'); - $action = CRM_Utils_Request::retrieve('action', 'String'); - $mode = CRM_Utils_Request::retrieve('mode', 'String', CRM_Core_DAO::$_nullObject, FALSE, 'safe'); - - $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid); - $urlQry = "reset=1&action=update&rgid={$rgid}&gid={$gid}&limit={$limit}"; + $rgid = CRM_Utils_Request::retrieveValue('rgid', 'Positive'); + $gid = CRM_Utils_Request::retrieveValue('gid', 'Positive'); + $limit = CRM_Utils_Request::retrieveValue('limit', 'Positive'); + $action = CRM_Utils_Request::retrieveValue('action', 'String'); + $mode = CRM_Utils_Request::retrieveValue('mode', 'String', 'safe'); + $criteria = CRM_Utils_Request::retrieve('criteria', 'Json', $null, FALSE, '{}'); + + $urlQry = [ + 'reset' => 1, + 'action' => 'update', + 'rgid' => $rgid, + 'gid' => $gid, + 'limit' => $limit, + 'criteria' => $criteria, + ]; + + $criteria = json_decode($criteria, TRUE); + $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($rgid, $gid, $criteria, TRUE, $limit); if ($mode == 'aggressive' && !CRM_Core_Permission::check('force merge duplicate contacts')) { CRM_Core_Session::setStatus(ts('You do not have permission to force merge duplicate contact records'), ts('Permission Denied'), 'error'); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); } // Setup the Queue - $queue = CRM_Queue_Service::singleton()->create(array( + $queue = CRM_Queue_Service::singleton()->create([ 'name' => $cacheKeyString, 'type' => 'Sql', 'reset' => TRUE, - )); + ]); $where = NULL; - if ($action == CRM_Core_Action::MAP) { - $where = "pn.is_selected = 1"; - $isSelected = 1; - } - else { - // else merge all (2) - $isSelected = 2; - } + $onlyProcessSelected = ($action == CRM_Core_Action::MAP) ? 1 : 0; - $total = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, $where); + $total = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, ($onlyProcessSelected ? "pn.is_selected = 1" : NULL)); if ($total <= 0) { // Nothing to do. CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry)); @@ -96,9 +96,9 @@ public static function getRunner() { CRM_Dedupe_Merger::resetMergeStats($cacheKeyString); for ($i = 1; $i <= ceil($total / self::BATCHLIMIT); $i++) { - $task = new CRM_Queue_Task( - array('CRM_Contact_Page_DedupeMerge', 'callBatchMerge'), - array($rgid, $gid, $mode, self::BATCHLIMIT, $isSelected), + $task = new CRM_Queue_Task( + ['CRM_Contact_Page_DedupeMerge', 'callBatchMerge'], + [$rgid, $gid, $mode, self::BATCHLIMIT, $onlyProcessSelected, $criteria, $limit], "Processed " . $i * self::BATCHLIMIT . " pair of duplicates out of " . $total ); @@ -107,13 +107,16 @@ public static function getRunner() { } // Setup the Runner - $urlQry .= "&context=conflicts"; - $runner = new CRM_Queue_Runner(array( + $urlQry['context'] = "conflicts"; + if ($onlyProcessSelected) { + $urlQry['selected'] = 1; + } + $runner = new CRM_Queue_Runner([ 'title' => ts('Merging Duplicates..'), 'queue' => $queue, 'errorMode' => CRM_Queue_Runner::ERROR_ABORT, 'onEndUrl' => CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry, TRUE, NULL, FALSE), - )); + ]); return $runner; } @@ -128,11 +131,16 @@ public static function getRunner() { * 'safe' mode or 'force' mode. * @param int $batchLimit * @param int $isSelected + * @param array $criteria + * @param int $searchLimit * * @return int + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ - public static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid, $mode = 'safe', $batchLimit, $isSelected) { - CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $batchLimit, $isSelected); + public static function callBatchMerge(CRM_Queue_TaskContext $ctx, $rgid, $gid, $mode = 'safe', $batchLimit, $isSelected, $criteria, $searchLimit) { + CRM_Dedupe_Merger::batchMerge($rgid, $gid, $mode, $batchLimit, $isSelected, $criteria, TRUE, FALSE, $searchLimit); return CRM_Queue_Task::TASK_SUCCESS; } diff --git a/CRM/Contact/Page/DedupeRules.php b/CRM/Contact/Page/DedupeRules.php index 151dcdf58eaa..aa5fcfb11739 100644 --- a/CRM/Contact/Page/DedupeRules.php +++ b/CRM/Contact/Page/DedupeRules.php @@ -1,9 +1,9 @@ ts('Use Rule'), 'url' => 'civicrm/contact/dedupefind', 'qs' => 'reset=1&rgid=%%id%%&action=preview', 'title' => ts('Use DedupeRule'), - ); + ]; } if (CRM_Core_Permission::check('administer dedupe rules')) { - $links[CRM_Core_Action::UPDATE] = array( + $links[CRM_Core_Action::UPDATE] = [ 'name' => ts('Edit Rule'), 'url' => 'civicrm/contact/deduperules', 'qs' => 'action=update&id=%%id%%', 'title' => ts('Edit DedupeRule'), - ); - $links[CRM_Core_Action::DELETE] = array( + ]; + $links[CRM_Core_Action::DELETE] = [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/deduperules', 'qs' => 'action=delete&id=%%id%%', 'extra' => 'onclick = "return confirm(\'' . $deleteExtra . '\');"', 'title' => ts('Delete DedupeRule'), - ); + ]; } self::$_links = $links; @@ -99,14 +99,9 @@ public function &links() { * method. */ public function run() { - // get the requested action, default to 'browse' - $action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); + $id = $this->getIdAndAction(); - // assign vars to templates - $this->assign('action', $action); - $id = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE, 0); - - $context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE); if ($context == 'nonDupe') { CRM_Core_Session::setStatus(ts('Selected contacts have been marked as not duplicates'), ts('Changes Saved'), 'success'); } @@ -116,18 +111,18 @@ public function run() { $this->assign('hasperm_merge_duplicate_contacts', CRM_Core_Permission::check('merge duplicate contacts')); // which action to take? - if ($action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) { - $this->edit($action, $id); + if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) { + $this->edit($this->_action, $id); } - if ($action & CRM_Core_Action::DELETE) { + if ($this->_action & CRM_Core_Action::DELETE) { $this->delete($id); } // browse the rules $this->browse(); - // parent run - return parent::run(); + // This replaces parent run, but do parent's parent run + return CRM_Core_Page::run(); } /** @@ -135,14 +130,14 @@ public function run() { */ public function browse() { // get all rule groups - $ruleGroups = array(); + $ruleGroups = []; $dao = new CRM_Dedupe_DAO_RuleGroup(); - $dao->orderBy('contact_type,used ASC'); + $dao->orderBy('contact_type ASC, used ASC, title ASC'); $dao->find(); $dedupeRuleTypes = CRM_Core_SelectValues::getDedupeRuleTypes(); while ($dao->fetch()) { - $ruleGroups[$dao->contact_type][$dao->id] = array(); + $ruleGroups[$dao->contact_type][$dao->id] = []; CRM_Core_DAO::storeValues($dao, $ruleGroups[$dao->contact_type][$dao->id]); // form all action links @@ -160,7 +155,7 @@ public function browse() { $ruleGroups[$dao->contact_type][$dao->id]['action'] = CRM_Core_Action::formLink( $links, $action, - array('id' => $dao->id), + ['id' => $dao->id], ts('more'), FALSE, 'dedupeRule.manage.action', @@ -217,7 +212,7 @@ public function delete($id) { $rgDao->id = $id; if ($rgDao->find(TRUE)) { $rgDao->delete(); - CRM_Core_Session::setStatus(ts("The rule '%1' has been deleted.", array(1 => $rgDao->title)), ts('Rule Deleted'), 'success'); + CRM_Core_Session::setStatus(ts("The rule '%1' has been deleted.", [1 => $rgDao->title]), ts('Rule Deleted'), 'success'); CRM_Utils_System::redirect(CRM_Utils_System::url($this->userContext(), 'reset=1')); } } diff --git a/CRM/Contact/Page/ImageFile.php b/CRM/Contact/Page/ImageFile.php index 60709a95cd3f..a567356a53cf 100644 --- a/CRM/Contact/Page/ImageFile.php +++ b/CRM/Contact/Page/ImageFile.php @@ -1,9 +1,9 @@ array("%" . $_GET['photo'], 'String'), - ); + $params = [ + 1 => ["%" . $_GET['photo'], 'String'], + ]; $dao = CRM_Core_DAO::executeQuery($sql, $params); $cid = NULL; while ($dao->fetch()) { @@ -69,7 +71,7 @@ public function run() { CRM_Utils_System::civiExit(); } else { - CRM_Core_Error::fatal('Photo does not exist'); + throw new CRM_Core_Exception(ts('Photo does not exist')); } } diff --git a/CRM/Contact/Page/Inline/Actions.php b/CRM/Contact/Page/Inline/Actions.php index cfa30081a798..b1d63401702a 100644 --- a/CRM/Contact/Page/Inline/Actions.php +++ b/CRM/Contact/Page/Inline/Actions.php @@ -1,9 +1,9 @@ 0) { - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array('labelColumn' => 'display_name')); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'display_name']); - $entityBlock = array('id' => $addressId); + $entityBlock = ['id' => $addressId]; $address = CRM_Core_BAO_Address::getValues($entityBlock, FALSE, 'id'); if (!empty($address)) { foreach ($address as $key => & $value) { @@ -65,16 +65,16 @@ public function run() { if (!empty($currentAddressBlock['address'][$locBlockNo])) { // get contact name of shared contact names - $sharedAddresses = array(); + $sharedAddresses = []; $shareAddressContactNames = CRM_Contact_BAO_Contact_Utils::getAddressShareContactNames($currentAddressBlock['address']); foreach ($currentAddressBlock['address'] as $key => $addressValue) { if (!empty($addressValue['master_id']) && !$shareAddressContactNames[$addressValue['master_id']]['is_deleted'] ) { - $sharedAddresses[$key]['shared_address_display'] = array( + $sharedAddresses[$key]['shared_address_display'] = [ 'address' => $addressValue['display'], 'name' => $shareAddressContactNames[$addressValue['master_id']]['name'], - ); + ]; } } $idValue = $currentAddressBlock['address'][$locBlockNo]['id']; @@ -83,9 +83,7 @@ public function run() { } // add custom data of type address - $groupTree = CRM_Core_BAO_CustomGroup::getTree('Address', - $this, $idValue - ); + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Address', NULL, $idValue); // we setting the prefix to dnc_ below so that we don't overwrite smarty's grouptree var. $currentAddressBlock['address'][$locBlockNo]['custom'] = CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, "dnc_"); @@ -97,7 +95,7 @@ public function run() { $contact = new CRM_Contact_BAO_Contact(); $contact->id = $contactId; $contact->find(TRUE); - $privacy = array(); + $privacy = []; foreach (CRM_Contact_BAO_Contact::$_commPrefs as $name) { if (isset($contact->$name)) { $privacy[$name] = $contact->$name; diff --git a/CRM/Contact/Page/Inline/CommunicationPreferences.php b/CRM/Contact/Page/Inline/CommunicationPreferences.php index bf1978ba2f95..c6d480a90282 100644 --- a/CRM/Contact/Page/Inline/CommunicationPreferences.php +++ b/CRM/Contact/Page/Inline/CommunicationPreferences.php @@ -1,9 +1,9 @@ $contactId); + $params = ['id' => $contactId]; - $defaults = array(); + $defaults = []; CRM_Contact_BAO_Contact::getValues($params, $defaults); $defaults['privacy_values'] = CRM_Core_SelectValues::privacy(); diff --git a/CRM/Contact/Page/Inline/ContactInfo.php b/CRM/Contact/Page/Inline/ContactInfo.php index 80c0ae1a4528..3435e1935660 100644 --- a/CRM/Contact/Page/Inline/ContactInfo.php +++ b/CRM/Contact/Page/Inline/ContactInfo.php @@ -1,9 +1,9 @@ $contactId); + $params = ['id' => $contactId]; - $defaults = array(); + $defaults = []; CRM_Contact_BAO_Contact::getValues($params, $defaults); //get the current employer name diff --git a/CRM/Contact/Page/Inline/ContactName.php b/CRM/Contact/Page/Inline/ContactName.php index ff84f8d1fc19..ba9cfc187470 100644 --- a/CRM/Contact/Page/Inline/ContactName.php +++ b/CRM/Contact/Page/Inline/ContactName.php @@ -1,9 +1,9 @@ $contactId); + $params = ['id' => $contactId]; - $defaults = array(); + $defaults = []; CRM_Contact_BAO_Contact::getValues($params, $defaults); if (!empty($defaults['gender_id'])) { diff --git a/CRM/Contact/Page/Inline/Email.php b/CRM/Contact/Page/Inline/Email.php index 2e69d8446995..9a257e1a3bd1 100644 --- a/CRM/Contact/Page/Inline/Email.php +++ b/CRM/Contact/Page/Inline/Email.php @@ -1,9 +1,9 @@ 'display_name')); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'display_name']); - $entityBlock = array('contact_id' => $contactId); + $entityBlock = ['contact_id' => $contactId]; $emails = CRM_Core_BAO_Email::getValues($entityBlock); if (!empty($emails)) { foreach ($emails as &$value) { @@ -58,7 +58,7 @@ public function run() { $contact = new CRM_Contact_BAO_Contact(); $contact->id = $contactId; $contact->find(TRUE); - $privacy = array(); + $privacy = []; foreach (CRM_Contact_BAO_Contact::$_commPrefs as $name) { if (isset($contact->$name)) { $privacy[$name] = $contact->$name; diff --git a/CRM/Contact/Page/Inline/IM.php b/CRM/Contact/Page/Inline/IM.php index 58d043410294..8b0fd8d52ce7 100644 --- a/CRM/Contact/Page/Inline/IM.php +++ b/CRM/Contact/Page/Inline/IM.php @@ -1,9 +1,9 @@ 'display_name')); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'display_name']); $IMProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'); - $entityBlock = array('contact_id' => $contactId); + $entityBlock = ['contact_id' => $contactId]; $ims = CRM_Core_BAO_IM::getValues($entityBlock); if (!empty($ims)) { foreach ($ims as $key => & $value) { diff --git a/CRM/Contact/Page/Inline/OpenID.php b/CRM/Contact/Page/Inline/OpenID.php index 7366d11dedf4..b20baf6e5db9 100644 --- a/CRM/Contact/Page/Inline/OpenID.php +++ b/CRM/Contact/Page/Inline/OpenID.php @@ -1,9 +1,9 @@ 'display_name')); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'display_name']); - $entityBlock = array('contact_id' => $contactId); + $entityBlock = ['contact_id' => $contactId]; $openids = CRM_Core_BAO_OpenID::getValues($entityBlock); if (!empty($openids)) { foreach ($openids as $key => & $value) { diff --git a/CRM/Contact/Page/Inline/Phone.php b/CRM/Contact/Page/Inline/Phone.php index d93ea6289038..4e78071fe3b7 100644 --- a/CRM/Contact/Page/Inline/Phone.php +++ b/CRM/Contact/Page/Inline/Phone.php @@ -1,9 +1,9 @@ 'display_name')); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'display_name']); $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'); - $entityBlock = array('contact_id' => $contactId); + $entityBlock = ['contact_id' => $contactId]; $phones = CRM_Core_BAO_Phone::getValues($entityBlock); if (!empty($phones)) { foreach ($phones as $key => & $value) { @@ -60,7 +60,7 @@ public function run() { $contact = new CRM_Contact_BAO_Contact(); $contact->id = $contactId; $contact->find(TRUE); - $privacy = array(); + $privacy = []; foreach (CRM_Contact_BAO_Contact::$_commPrefs as $name) { if (isset($contact->$name)) { $privacy[$name] = $contact->$name; diff --git a/CRM/Contact/Page/Inline/Website.php b/CRM/Contact/Page/Inline/Website.php index ef29b1c7af49..5e9ce5ea2c0e 100644 --- a/CRM/Contact/Page/Inline/Website.php +++ b/CRM/Contact/Page/Inline/Website.php @@ -1,9 +1,9 @@ $contactId); - $websites = CRM_Core_BAO_Website::getValues($params, CRM_Core_DAO::$_nullArray); + $params = ['contact_id' => $contactId]; + $websites = CRM_Core_BAO_Website::getValues($params); if (!empty($websites)) { foreach ($websites as $key => & $value) { $value['website_type'] = $websiteTypes[$value['website_type_id']]; diff --git a/CRM/Contact/Page/SavedSearch.php b/CRM/Contact/Page/SavedSearch.php index ab9f39bd2dee..a101e86283ba 100644 --- a/CRM/Contact/Page/SavedSearch.php +++ b/CRM/Contact/Page/SavedSearch.php @@ -1,9 +1,9 @@ is_active = 1; $savedSearch->selectAdd(); $savedSearch->selectAdd('id, form_values'); $savedSearch->find(); - $properties = array('id', 'name', 'description'); + $properties = ['id', 'name', 'description']; while ($savedSearch->fetch()) { // get name and description from group object $group = new CRM_Contact_DAO_Group(); @@ -85,7 +85,7 @@ public function browse() { if ($group->find(TRUE)) { $permissions = CRM_Contact_BAO_Group::checkPermission($group->id, TRUE); if (!CRM_Utils_System::isNull($permissions)) { - $row = array(); + $row = []; $row['name'] = $group->title; $row['description'] = $group->description; @@ -100,7 +100,7 @@ public function browse() { $row['action'] = CRM_Core_Action::formLink( self::links(), $action, - array('id' => $row['id']), + ['id' => $row['id']], ts('more'), FALSE, 'savedSearch.manage.action', @@ -148,20 +148,20 @@ public static function &links() { $deleteExtra = ts('Do you really want to remove this Smart Group?'); - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('Search'), 'url' => 'civicrm/contact/search/advanced', 'qs' => 'reset=1&force=1&ssID=%%id%%', 'title' => ts('Search'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/search/saved', 'qs' => 'action=delete&id=%%id%%', 'extra' => 'onclick="return confirm(\'' . $deleteExtra . '\');"', - ), - ); + ], + ]; } return self::$_links; } diff --git a/CRM/Contact/Page/Task.php b/CRM/Contact/Page/Task.php index 0a33c62d07bb..af6ce6cf9e28 100644 --- a/CRM/Contact/Page/Task.php +++ b/CRM/Contact/Page/Task.php @@ -1,9 +1,9 @@ _contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); + $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this); } else { $this->_contactId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_GroupContact', $gcid, 'contact_id'); @@ -101,7 +101,7 @@ public function preProcess() { // ensure that the id does exist if (CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'id') != $this->_contactId) { CRM_Core_Error::statusBounce( - ts('A Contact with that ID does not exist: %1', array(1 => $this->_contactId)), + ts('A Contact with that ID does not exist: %1', [1 => $this->_contactId]), CRM_Utils_System::url('civicrm/dashboard', 'reset=1') ); } @@ -109,15 +109,15 @@ public function preProcess() { $this->assign('contactId', $this->_contactId); // see if we can get prev/next positions from qfKey - $navContacts = array( + $navContacts = [ 'prevContactID' => NULL, 'prevContactName' => NULL, 'nextContactID' => NULL, 'nextContactName' => NULL, 'nextPrevError' => 0, - ); + ]; if ($qfKey) { - $pos = CRM_Core_BAO_PrevNextCache::getPositions("civicrm search $qfKey", + $pos = Civi::service('prevnext')->getPositions("civicrm search $qfKey", $this->_contactId, $this->_contactId ); @@ -146,18 +146,18 @@ public function preProcess() { } elseif ($context) { $this->assign('context', $context); - CRM_Utils_System::appendBreadCrumb(array( - array( + CRM_Utils_System::appendBreadCrumb([ + [ 'title' => ts('Search Results'), - 'url' => CRM_Utils_System::url("civicrm/contact/search/$context", array('qfKey' => $qfKey)), - ), - )); + 'url' => CRM_Utils_System::url("civicrm/contact/search/$context", ['qfKey' => $qfKey]), + ], + ]); } } $this->assign($navContacts); $path = CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $this->_contactId); - CRM_Utils_System::appendBreadCrumb(array(array('title' => ts('View Contact'), 'url' => $path))); + CRM_Utils_System::appendBreadCrumb([['title' => ts('View Contact'), 'url' => $path]]); if ($image_URL = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'image_URL')) { $this->assign("imageURL", CRM_Utils_File::getImageURL($image_URL)); @@ -184,11 +184,11 @@ public function preProcess() { // add to recently viewed block $isDeleted = (bool) CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'is_deleted'); - $recentOther = array( + $recentOther = [ 'imageUrl' => $contactImageUrl, 'subtype' => $contactSubtype, 'isDeleted' => $isDeleted, - ); + ]; if (CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT)) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/add', "reset=1&action=update&cid={$this->_contactId}"); @@ -306,14 +306,14 @@ public static function checkUserPermission($page, $contactID = NULL) { */ public static function setTitle($contactId, $isDeleted = FALSE) { static $contactDetails; - $displayName = $contactImage = NULL; + $contactImage = NULL; if (!isset($contactDetails[$contactId])) { list($displayName, $contactImage) = self::getContactDetails($contactId); - $contactDetails[$contactId] = array( + $contactDetails[$contactId] = [ 'displayName' => $displayName, 'contactImage' => $contactImage, 'isDeceased' => (bool) CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactId, 'is_deceased'), - ); + ]; } else { $displayName = $contactDetails[$contactId]['displayName']; @@ -323,10 +323,19 @@ public static function setTitle($contactId, $isDeleted = FALSE) { // set page title $title = "{$contactImage} {$displayName}"; if ($contactDetails[$contactId]['isDeceased']) { - $title .= ' (deceased)'; + $title .= ' (' . ts('deceased') . ')'; } if ($isDeleted) { $title = "{$title}"; + $mergedTo = civicrm_api3('Contact', 'getmergedto', ['contact_id' => $contactId, 'api.Contact.get' => ['return' => 'display_name']]); + if ($mergedTo['count']) { + $mergedToContactID = $mergedTo['id']; + $mergedToDisplayName = $mergedTo['values'][$mergedToContactID]['api.Contact.get']['values'][0]['display_name']; + $title .= ' ' . ts('(This contact has been merged to %2)', [ + 1 => CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $mergedToContactID]), + 2 => $mergedToDisplayName, + ]); + } } // Inline-edit places its own title on the page @@ -361,7 +370,7 @@ public static function addUrls(&$obj, $cid) { } // See if other modules want to add links to the activtity bar - $hookLinks = array(); + $hookLinks = []; CRM_Utils_Hook::links('view.contact.activity', 'Contact', $cid, diff --git a/CRM/Contact/Page/View/ContactSmartGroup.php b/CRM/Contact/Page/View/ContactSmartGroup.php index 57200cbf6031..0c323100a45e 100644 --- a/CRM/Contact/Page/View/ContactSmartGroup.php +++ b/CRM/Contact/Page/View/ContactSmartGroup.php @@ -1,9 +1,9 @@ _contactId, 'Added'); // keep track of all 'added' contact groups so we can remove them from the smart group // section - $staticGroups = array(); + $staticGroups = []; if (!empty($in)) { foreach ($in as $group) { $staticGroups[$group['group_id']] = 1; @@ -58,7 +59,7 @@ public function browse() { $this->assign('groupParent', NULL); if (!empty($allGroup)) { - $smart = $parent = array(); + $smart = $parent = []; foreach ($allGroup['group'] as $group) { // delete all smart groups which are also in static groups if (isset($staticGroups[$group['id']])) { diff --git a/CRM/Contact/Page/View/CustomData.php b/CRM/Contact/Page/View/CustomData.php index 4ecfa0bcd634..73d975338209 100644 --- a/CRM/Contact/Page/View/CustomData.php +++ b/CRM/Contact/Page/View/CustomData.php @@ -1,9 +1,9 @@ _multiRecordDisplay == 'single') { $groupTitle = CRM_Core_BAO_CustomGroup::getTitle($this->_groupId); - CRM_Utils_System::setTitle(ts('View %1 Record', array(1 => $groupTitle))); - $groupTree = CRM_Core_BAO_CustomGroup::getTree($entityType, $this, $this->_contactId, + CRM_Utils_System::setTitle(ts('View %1 Record', [1 => $groupTitle])); + $groupTree = CRM_Core_BAO_CustomGroup::getTree($entityType, NULL, $this->_contactId, $this->_groupId, $entitySubType, NULL, TRUE, NULL, FALSE, TRUE, $this->_cgcount ); @@ -149,7 +149,7 @@ public function run() { $this->assign('skipTitle', 1); } else { - $groupTree = CRM_Core_BAO_CustomGroup::getTree($entityType, $this, $this->_contactId, + $groupTree = CRM_Core_BAO_CustomGroup::getTree($entityType, NULL, $this->_contactId, $this->_groupId, $entitySubType ); } diff --git a/CRM/Contact/Page/View/GroupContact.php b/CRM/Contact/Page/View/GroupContact.php index ea767bf394ee..88178d4b4764 100644 --- a/CRM/Contact/Page/View/GroupContact.php +++ b/CRM/Contact/Page/View/GroupContact.php @@ -1,9 +1,9 @@ orderBy('modified_date desc'); $log->find(); - $logEntries = array(); + $logEntries = []; while ($log->fetch()) { list($displayName, $contactImage) = CRM_Contact_BAO_Contact::getDisplayAndImage($log->modified_id); - $logEntries[] = array( + $logEntries[] = [ 'id' => $log->modified_id, 'name' => $displayName, 'image' => $contactImage, 'date' => $log->modified_date, - ); + ]; } $this->assign('logCount', count($logEntries)); diff --git a/CRM/Contact/Page/View/Note.php b/CRM/Contact/Page/View/Note.php index 7eb47d4c2f55..9fe30dc03bdf 100644 --- a/CRM/Contact/Page/View/Note.php +++ b/CRM/Contact/Page/View/Note.php @@ -1,9 +1,9 @@ id = $this->_id; if ($note->find(TRUE)) { - $values = array(); - CRM_Core_DAO::storeValues($note, $values); - $values['privacy'] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Note', 'privacy', $values['privacy']); - $this->assign('note', $values); + CRM_Core_DAO::storeValues($note, $this->values); + $this->values['privacy'] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Note', 'privacy', $this->values['privacy']); + $this->assign('note', $this->values); } - $comments = CRM_Core_BAO_Note::getNoteTree($values['id'], 1); + $comments = CRM_Core_BAO_Note::getNoteTree($this->values['id'], 1); if (!empty($comments)) { $this->assign('comments', $comments); } @@ -86,7 +91,7 @@ public function browse() { $note->orderBy('modified_date desc'); //CRM-4418, handling edit and delete separately. - $permissions = array($this->_permission); + $permissions = [$this->_permission]; if ($this->_permission == CRM_Core_Permission::EDIT) { //previously delete was subset of edit //so for consistency lets grant delete also. @@ -94,41 +99,43 @@ public function browse() { } $mask = CRM_Core_Action::mask($permissions); - $values = array(); + $this->assign('canAddNotes', CRM_Core_Permission::check('add contact notes')); + $links = self::links(); $action = array_sum(array_keys($links)) & $mask; $note->find(); while ($note->fetch()) { if (!CRM_Core_BAO_Note::getNotePrivacyHidden($note)) { - CRM_Core_DAO::storeValues($note, $values[$note->id]); + CRM_Core_DAO::storeValues($note, $this->values[$note->id]); - $values[$note->id]['action'] = CRM_Core_Action::formLink($links, + $this->values[$note->id]['action'] = CRM_Core_Action::formLink($links, $action, - array( + [ 'id' => $note->id, 'cid' => $this->_contactId, - ), + ], ts('more'), FALSE, 'note.selector.row', 'Note', $note->id ); - $contact = new CRM_Contact_DAO_Contact(); - $contact->id = $note->contact_id; - $contact->find(); - $contact->fetch(); - $values[$note->id]['createdBy'] = $contact->display_name; - $values[$note->id]['comment_count'] = CRM_Core_BAO_Note::getChildCount($note->id); + if (!empty($note->contact_id)) { + $contact = new CRM_Contact_DAO_Contact(); + $contact->id = $note->contact_id; + $contact->find(); + $contact->fetch(); + $this->values[$note->id]['createdBy'] = $contact->display_name; + } + $this->values[$note->id]['comment_count'] = CRM_Core_BAO_Note::getChildCount($note->id); // paper icon view for attachments part $paperIconAttachmentInfo = CRM_Core_BAO_File::paperIconAttachment('civicrm_note', $note->id); - $values[$note->id]['attachment'] = $paperIconAttachmentInfo; + $this->values[$note->id]['attachment'] = $paperIconAttachmentInfo; } } - - $this->assign('notes', $values); + $this->assign('notes', $this->values); $commentLinks = self::commentLinks(); @@ -136,11 +143,11 @@ public function browse() { $commentAction = CRM_Core_Action::formLink($commentLinks, $action, - array( + [ 'id' => $note->id, 'pid' => $note->entity_id, 'cid' => $note->entity_id, - ), + ], ts('more'), FALSE, 'note.comment.action', @@ -212,10 +219,27 @@ public function run() { if ($this->_action & CRM_Core_Action::VIEW) { $this->view(); } - elseif ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) { + elseif ($this->_action & CRM_Core_Action::ADD) { + if ( + $this->_permission != CRM_Core_Permission::EDIT && + !CRM_Core_Permission::check('add contact notes') + ) { + CRM_Core_Error::statusBounce(ts('You do not have access to add notes.')); + } + + $this->edit(); + } + elseif ($this->_action & CRM_Core_Action::UPDATE) { + if ($this->_permission != CRM_Core_Permission::EDIT) { + CRM_Core_Error::statusBounce(ts('You do not have access to edit this note.')); + } + $this->edit(); } elseif ($this->_action & CRM_Core_Action::DELETE) { + if ($this->_permission != CRM_Core_Permission::EDIT) { + CRM_Core_Error::statusBounce(ts('You do not have access to delete this note.')); + } // we use the edit screen the confirm the delete $this->edit(); } @@ -241,32 +265,32 @@ public static function &links() { if (!(self::$_links)) { $deleteExtra = ts('Are you sure you want to delete this note?'); - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('View'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=view&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note', 'title' => ts('View Note'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=update&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note', 'title' => ts('Edit Note'), - ), - CRM_Core_Action::ADD => array( + ], + CRM_Core_Action::ADD => [ 'name' => ts('Comment'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=add&reset=1&cid=%%cid%%&parentId=%%id%%&selectedChild=note', 'title' => ts('Add Comment'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=delete&reset=1&cid=%%cid%%&id=%%id%%&selectedChild=note', 'title' => ts('Delete Note'), - ), - ); + ], + ]; } return self::$_links; } @@ -279,26 +303,26 @@ public static function &links() { */ public static function &commentLinks() { if (!(self::$_commentLinks)) { - self::$_commentLinks = array( - CRM_Core_Action::VIEW => array( + self::$_commentLinks = [ + CRM_Core_Action::VIEW => [ 'name' => ts('View'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=view&reset=1&cid=%%cid%%&id={id}&selectedChild=note', 'title' => ts('View Comment'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=update&reset=1&cid=%%cid%%&id={id}&parentId=%%pid%%&selectedChild=note', 'title' => ts('Edit Comment'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/note', 'qs' => 'action=delete&reset=1&cid=%%cid%%&id={id}&selectedChild=note', 'title' => ts('Delete Comment'), - ), - ); + ], + ]; } return self::$_commentLinks; } diff --git a/CRM/Contact/Page/View/Print.php b/CRM/Contact/Page/View/Print.php index 455137705790..7ebbd4042704 100644 --- a/CRM/Contact/Page/View/Print.php +++ b/CRM/Contact/Page/View/Print.php @@ -1,9 +1,9 @@ _contactId; $contact = CRM_Contact_BAO_Contact::retrieve($params, $defaults, $ids); diff --git a/CRM/Contact/Page/View/Relationship.php b/CRM/Contact/Page/View/Relationship.php index 5e886b0f1060..f6004c1090e9 100644 --- a/CRM/Contact/Page/View/Relationship.php +++ b/CRM/Contact/Page/View/Relationship.php @@ -1,9 +1,9 @@ _caseId = $caseId; + } + + /** + * @return int + */ + public function getCaseId() { + return $this->_caseId; + } + + /** + * Explicitly declare the entity api name. + * + * @return string + */ + public function getDefaultEntity() { + return 'Relationship'; + } + + /** + * Explicitly declare the form context. + * + * @return string|null + */ + public function getDefaultContext() { + return 'search'; + } /** * View details of a relationship. */ public function view() { - $viewRelationship = CRM_Contact_BAO_Relationship::getRelationship($this->_contactId, NULL, NULL, NULL, $this->_id); + $viewRelationship = CRM_Contact_BAO_Relationship::getRelationship($this->getContactId(), NULL, NULL, NULL, $this->getEntityId()); //To check whether selected contact is a contact_id_a in //relationship type 'a_b' in relationship table, if yes then //revert the permissionship text in template $relationship = new CRM_Contact_DAO_Relationship(); - $relationship->id = $viewRelationship[$this->_id]['id']; + $relationship->id = $viewRelationship[$this->getEntityId()]['id']; if ($relationship->find(TRUE)) { - if (($viewRelationship[$this->_id]['rtype'] == 'a_b') && ($this->_contactId == $relationship->contact_id_a)) { + if (($viewRelationship[$this->getEntityId()]['rtype'] == 'a_b') && ($this->getContactId() == $relationship->contact_id_a)) { $this->assign("is_contact_id_a", TRUE); } } - $relType = $viewRelationship[$this->_id]['civicrm_relationship_type_id']; + $relType = $viewRelationship[$this->getEntityId()]['civicrm_relationship_type_id']; $this->assign('viewRelationship', $viewRelationship); - $employerId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactId, 'employer_id'); + $employerId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->getContactId(), 'employer_id'); $this->assign('isCurrentEmployer', FALSE); $relTypes = CRM_Utils_Array::index(array('name_a_b'), CRM_Core_PseudoConstant::relationshipType('name')); - if ($viewRelationship[$this->_id]['employer_id'] == $this->_contactId) { + if ($viewRelationship[$this->getEntityId()]['employer_id'] == $this->getContactId()) { $this->assign('isCurrentEmployer', TRUE); } elseif ($relType == $relTypes['Employee of']['id'] && - ($viewRelationship[$this->_id]['cid'] == $employerId) + ($viewRelationship[$this->getEntityId()]['cid'] == $employerId) ) { // make sure we are viewing employee of relationship $this->assign('isCurrentEmployer', TRUE); } - $viewNote = CRM_Core_BAO_Note::getNote($this->_id); + $viewNote = CRM_Core_BAO_Note::getNote($this->getEntityId()); $this->assign('viewNote', $viewNote); - $groupTree = CRM_Core_BAO_CustomGroup::getTree('Relationship', $this, $this->_id, 0, $relType); - CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $this->_id); + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Relationship', NULL, $this->getEntityId(), 0, $relType); + CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $this->getEntityId()); - $rType = CRM_Utils_Array::value('rtype', $viewRelationship[$this->_id]); + $rType = CRM_Utils_Array::value('rtype', $viewRelationship[$this->getEntityId()]); // add viewed contribution to recent items list $url = CRM_Utils_System::url('civicrm/contact/view/rel', - "action=view&reset=1&id={$viewRelationship[$this->_id]['id']}&cid={$this->_contactId}&context=home" + "action=view&reset=1&id={$viewRelationship[$this->getEntityId()]['id']}&cid={$this->getContactId()}&context=home" ); $session = CRM_Core_Session::singleton(); $recentOther = array(); - if (($session->get('userID') == $this->_contactId) || - CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT) + if (($session->get('userID') == $this->getContactId()) || + CRM_Contact_BAO_Contact_Permission::allow($this->getContactId(), CRM_Core_Permission::EDIT) ) { $recentOther = array( 'editUrl' => CRM_Utils_System::url('civicrm/contact/view/rel', - "action=update&reset=1&id={$viewRelationship[$this->_id]['id']}&cid={$this->_contactId}&rtype={$rType}&context=home" + "action=update&reset=1&id={$viewRelationship[$this->getEntityId()]['id']}&cid={$this->getContactId()}&rtype={$rType}&context=home" ), 'deleteUrl' => CRM_Utils_System::url('civicrm/contact/view/rel', - "action=delete&reset=1&id={$viewRelationship[$this->_id]['id']}&cid={$this->_contactId}&rtype={$rType}&context=home" + "action=delete&reset=1&id={$viewRelationship[$this->getEntityId()]['id']}&cid={$this->getContactId()}&rtype={$rType}&context=home" ), ); } - $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); + $displayName = CRM_Contact_BAO_Contact::displayName($this->getContactId()); $this->assign('displayName', $displayName); CRM_Utils_System::setTitle(ts('View Relationship for') . ' ' . $displayName); - $title = $displayName . ' (' . $viewRelationship[$this->_id]['relation'] . ' ' . CRM_Contact_BAO_Contact::displayName($viewRelationship[$this->_id]['cid']) . ')'; + $title = $displayName . ' (' . $viewRelationship[$this->getEntityId()]['relation'] . ' ' . CRM_Contact_BAO_Contact::displayName($viewRelationship[$this->getEntityId()]['cid']) . ')'; // add the recently viewed Relationship CRM_Utils_Recent::add($title, $url, - $viewRelationship[$this->_id]['id'], + $viewRelationship[$this->getEntityId()]['id'], 'Relationship', - $this->_contactId, + $this->getContactId(), NULL, $recentOther ); @@ -134,6 +158,10 @@ public function view() { */ public function browse() { // do nothing :) we are using datatable for rendering relationship selectors + $columnHeaders = CRM_Contact_BAO_Relationship::getColumnHeaders(); + $contactRelationships = $selector = NULL; + CRM_Utils_Hook::searchColumns('relationship.columns', $columnHeaders, $contactRelationships, $selector); + $this->assign('columnHeaders', $columnHeaders); } /** @@ -141,70 +169,59 @@ public function browse() { * */ public function edit() { - $controller = new CRM_Core_Controller_Simple('CRM_Contact_Form_Relationship', ts('Contact Relationships'), $this->_action); + $controller = new CRM_Core_Controller_Simple('CRM_Contact_Form_Relationship', ts('Contact Relationships'), $this->getAction()); $controller->setEmbedded(TRUE); // set the userContext stack $session = CRM_Core_Session::singleton(); // if this is called from case view, we need to redirect back to same page - if ($this->_caseId) { - $url = CRM_Utils_System::url('civicrm/contact/view/case', "action=view&reset=1&cid={$this->_contactId}&id={$this->_caseId}"); + if ($this->getCaseId()) { + $url = CRM_Utils_System::url('civicrm/contact/view/case', "action=view&reset=1&cid={$this->getContactId()}&id={$this->getCaseId()}"); } else { - $url = CRM_Utils_System::url('civicrm/contact/view', "action=browse&selectedChild=rel&reset=1&cid={$this->_contactId}"); + $url = CRM_Utils_System::url('civicrm/contact/view', "action=browse&selectedChild=rel&reset=1&cid={$this->getContactId()}"); } $session->pushUserContext($url); if (CRM_Utils_Request::retrieve('confirmed', 'Boolean')) { - if ($this->_caseId) { + if ($this->getCaseId()) { //create an activity for case role removal.CRM-4480 - CRM_Case_BAO_Case::createCaseRoleActivity($this->_caseId, $this->_id); + CRM_Case_BAO_Case::createCaseRoleActivity($this->getCaseId(), $this->getEntityId()); CRM_Core_Session::setStatus(ts('Case Role has been deleted successfully.'), ts('Record Deleted'), 'success'); } // delete relationship - CRM_Contact_BAO_Relationship::del($this->_id); + CRM_Contact_BAO_Relationship::del($this->getEntityId()); CRM_Utils_System::redirect($url); } - $controller->set('contactId', $this->_contactId); - $controller->set('id', $this->_id); + $controller->set('contactId', $this->getContactId()); + $controller->set('id', $this->getEntityId()); $controller->process(); $controller->run(); } - public function preProcess() { - $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); - $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); - $this->assign('contactId', $this->_contactId); - - // check logged in url permission - CRM_Contact_Page_View::checkUserPermission($this); - - $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); - $this->assign('action', $this->_action); - } - /** * the main function that is called when the page loads, * it decides the which action has to be taken for the page. * * @return null + * @throws \CRM_Core_Exception */ public function run() { - $this->preProcess(); + $this->preProcessQuickEntityPage(); $this->setContext(); - $this->_caseId = CRM_Utils_Request::retrieve('caseID', 'Integer', $this); + $this->setCaseId(CRM_Utils_Request::retrieve('caseID', 'Integer', $this)); - if ($this->_action & CRM_Core_Action::VIEW) { + if ($this->isViewContext()) { $this->view(); } - elseif ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::DELETE)) { + elseif ($this->isEditContext() || $this->isDeleteContext()) { $this->edit(); } @@ -217,17 +234,8 @@ public function run() { } public function setContext() { - $context = CRM_Utils_Request::retrieve('context', 'String', - $this, FALSE, 'search' - ); - - if ($context == 'dashboard') { - $cid = CRM_Utils_Request::retrieve('cid', 'Integer', - $this, FALSE - ); - $url = CRM_Utils_System::url('civicrm/user', - "reset=1&id={$cid}" - ); + if ($this->getContext() == 'dashboard') { + $url = CRM_Utils_System::url('civicrm/user', "reset=1&id={$this->getContactId()}"); } else { $url = CRM_Utils_System::url('civicrm/contact/view', 'action=browse&selectedChild=rel'); @@ -242,7 +250,7 @@ public function setContext() { */ public function delete() { // calls a function to delete relationship - CRM_Contact_BAO_Relationship::del($this->_id); + CRM_Contact_BAO_Relationship::del($this->getEntityId()); } /** diff --git a/CRM/Contact/Page/View/Summary.php b/CRM/Contact/Page/View/Summary.php index ef4b9c6dfdc9..39594e4073dd 100644 --- a/CRM/Contact/Page/View/Summary.php +++ b/CRM/Contact/Page/View/Summary.php @@ -1,9 +1,9 @@ _contactId, NULL, $entitySubType @@ -122,7 +122,7 @@ public function view() { ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') ->addSetting(array( 'summaryPrint' => array('mode' => $this->_print), - 'tabSettings' => array('active' => CRM_Utils_Request::retrieve('selectedChild', 'String', $this, FALSE, 'summary')), + 'tabSettings' => array('active' => CRM_Utils_Request::retrieve('selectedChild', 'Alphanumeric', $this, FALSE, 'summary')), )); $this->assign('summaryPrint', $this->_print); $session = CRM_Core_Session::singleton(); @@ -182,10 +182,7 @@ public function view() { $idValue = $blockVal['master_id']; } } - $groupTree = CRM_Core_BAO_CustomGroup::getTree(ucfirst($key), - $this, - $idValue - ); + $groupTree = CRM_Core_BAO_CustomGroup::getTree(ucfirst($key), NULL, $idValue); // we setting the prefix to dnc_ below so that we don't overwrite smarty's grouptree var. $defaults[$key][$blockId]['custom'] = CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, "dnc_"); } @@ -267,23 +264,112 @@ public function view() { $lastModified = CRM_Core_BAO_Log::lastModified($this->_contactId, 'civicrm_contact'); $this->assign_by_ref('lastModified', $lastModified); - $allTabs = array(); - $weight = 10; - $this->_viewOptions = CRM_Core_BAO_Setting::valueOptions( CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_view_options', TRUE ); - // show the tabs only if user has generic access to CiviCRM - $accessCiviCRM = CRM_Core_Permission::check('access CiviCRM'); - $changeLog = $this->_viewOptions['log']; $this->assign_by_ref('changeLog', $changeLog); - $components = CRM_Core_Component::getEnabledComponents(); - foreach ($components as $name => $component) { + $this->assign('allTabs', $this->getTabs()); + + // hook for contact summary + // ignored but needed to prevent warnings + $contentPlacement = CRM_Utils_Hook::SUMMARY_BELOW; + CRM_Utils_Hook::summary($this->_contactId, $content, $contentPlacement); + if ($content) { + $this->assign_by_ref('hookContent', $content); + $this->assign('hookContentPlacement', $contentPlacement); + } + } + + /** + * @return string + */ + public function getTemplateFileName() { + if ($this->_contactId) { + $contactSubtypes = $this->get('contactSubtype') ? explode(CRM_Core_DAO::VALUE_SEPARATOR, $this->get('contactSubtype')) : array(); + + // there could be multiple subtypes. We check templates for each of the subtype, and return the first one found. + foreach ($contactSubtypes as $csType) { + if ($csType) { + $templateFile = "CRM/Contact/Page/View/SubType/{$csType}.tpl"; + $template = CRM_Core_Page::getTemplate(); + if ($template->template_exists($templateFile)) { + return $templateFile; + } + } + } + } + return parent::getTemplateFileName(); + } + + /** + * @return array + */ + public static function basicTabs() { + return [ + [ + 'id' => 'summary', + 'url' => '#contact-summary', + 'title' => ts('Summary'), + 'weight' => 0, + 'icon' => 'crm-i fa-address-card-o', + ], + [ + 'id' => 'activity', + 'title' => ts('Activities'), + 'class' => 'livePage', + 'weight' => 70, + 'icon' => 'crm-i fa-tasks', + ], + [ + 'id' => 'rel', + 'title' => ts('Relationships'), + 'class' => 'livePage', + 'weight' => 80, + 'icon' => 'crm-i fa-handshake-o', + ], + [ + 'id' => 'group', + 'title' => ts('Groups'), + 'class' => 'ajaxForm', + 'weight' => 90, + 'icon' => 'crm-i fa-users', + ], + [ + 'id' => 'note', + 'title' => ts('Notes'), + 'class' => 'livePage', + 'weight' => 100, + 'icon' => 'crm-i fa-sticky-note-o', + ], + [ + 'id' => 'tag', + 'title' => ts('Tags'), + 'weight' => 110, + 'icon' => 'crm-i fa-tags', + ], + [ + 'id' => 'log', + 'title' => ts('Change Log'), + 'weight' => 120, + 'icon' => 'crm-i fa-history', + ], + ]; + } + + /** + * @return array + * @throws \CRM_Core_Exception + */ + public function getTabs() { + $allTabs = []; + $weight = 10; + + foreach (CRM_Core_Component::getEnabledComponents() as $name => $component) { if (!empty($this->_viewOptions[$name]) && CRM_Core_Permission::access($component->name) ) { @@ -305,59 +391,30 @@ public function view() { if (CRM_Utils_Request::retrieve('isTest', 'Positive', $this)) { $q .= "&isTest=1"; } - $allTabs[] = array( + $allTabs[] = [ 'id' => $i, 'url' => CRM_Utils_System::url("civicrm/contact/view/$u", $q), 'title' => $elem['title'], 'weight' => $elem['weight'], 'count' => CRM_Contact_BAO_Contact::getCountComponent($u, $this->_contactId), 'class' => 'livePage', - ); - // make sure to get maximum weight, rest of tabs go after - // FIXME: not very elegant again - if ($weight < $elem['weight']) { - $weight = $elem['weight']; - } + 'icon' => $component->getIcon(), + ]; } } - $rest = array( - 'activity' => array( - 'title' => ts('Activities'), - 'class' => 'livePage', - ), - 'rel' => array( - 'title' => ts('Relationships'), - 'class' => 'livePage', - ), - 'group' => array( - 'title' => ts('Groups'), - 'class' => 'ajaxForm', - ), - 'note' => array( - 'title' => ts('Notes'), - 'class' => 'livePage', - ), - 'tag' => array( - 'title' => ts('Tags'), - ), - 'log' => array( - 'title' => ts('Change Log'), - ), - ); - - foreach ($rest as $k => $v) { - if ($accessCiviCRM && !empty($this->_viewOptions[$k])) { - $allTabs[] = $v + array( - 'id' => $k, - 'url' => CRM_Utils_System::url( - "civicrm/contact/view/$k", - "reset=1&cid={$this->_contactId}" - ), - 'weight' => $weight, - 'count' => CRM_Contact_BAO_Contact::getCountComponent($k, $this->_contactId), - ); - $weight += 10; + // show the tabs only if user has generic access to CiviCRM + $accessCiviCRM = CRM_Core_Permission::check('access CiviCRM'); + foreach (self::basicTabs() as $tab) { + if ($tab['id'] == 'summary') { + $allTabs[] = $tab; + } + elseif ($accessCiviCRM && !empty($this->_viewOptions[$tab['id']])) { + $allTabs[] = $tab + [ + 'url' => CRM_Utils_System::url("civicrm/contact/view/{$tab['id']}", "reset=1&cid={$this->_contactId}"), + 'count' => CRM_Contact_BAO_Contact::getCountComponent($tab['id'], $this->_contactId), + ]; + $weight = $tab['weight'] + 10; } } @@ -371,7 +428,7 @@ public function view() { foreach ($activeGroups as $group) { $id = "custom_{$group['id']}"; - $allTabs[] = array( + $allTabs[] = [ 'id' => $id, 'url' => CRM_Utils_System::url($group['path'], $group['query'] . "&selectedChild=$id"), 'title' => $group['title'], @@ -379,56 +436,19 @@ public function view() { 'count' => CRM_Contact_BAO_Contact::getCountComponent($id, $this->_contactId, $group['table_name']), 'hideCount' => !$group['is_multiple'], 'class' => 'livePage', - ); + 'icon' => 'crm-i fa-gear', + ]; $weight += 10; } - $context = array('contact_id' => $this->_contactId); + $context = ['contact_id' => $this->_contactId]; // see if any other modules want to add any tabs CRM_Utils_Hook::tabs($allTabs, $this->_contactId); CRM_Utils_Hook::tabset('civicrm/contact/view', $allTabs, $context); - $allTabs[] = array( - 'id' => 'summary', - 'url' => '#contact-summary', - 'title' => ts('Summary'), - 'weight' => 0, - ); - // now sort the tabs based on weight - usort($allTabs, array('CRM_Utils_Sort', 'cmpFunc')); - - $this->assign('allTabs', $allTabs); - - // hook for contact summary - // ignored but needed to prevent warnings - $contentPlacement = CRM_Utils_Hook::SUMMARY_BELOW; - CRM_Utils_Hook::summary($this->_contactId, $content, $contentPlacement); - if ($content) { - $this->assign_by_ref('hookContent', $content); - $this->assign('hookContentPlacement', $contentPlacement); - } - } - - /** - * @return string - */ - public function getTemplateFileName() { - if ($this->_contactId) { - $contactSubtypes = $this->get('contactSubtype') ? explode(CRM_Core_DAO::VALUE_SEPARATOR, $this->get('contactSubtype')) : array(); - - // there could be multiple subtypes. We check templates for each of the subtype, and return the first one found. - foreach ($contactSubtypes as $csType) { - if ($csType) { - $templateFile = "CRM/Contact/Page/View/SubType/{$csType}.tpl"; - $template = CRM_Core_Page::getTemplate(); - if ($template->template_exists($templateFile)) { - return $templateFile; - } - } - } - } - return parent::getTemplateFileName(); + usort($allTabs, ['CRM_Utils_Sort', 'cmpFunc']); + return $allTabs; } } diff --git a/CRM/Contact/Page/View/Tag.php b/CRM/Contact/Page/View/Tag.php index e70288998a21..009147271782 100644 --- a/CRM/Contact/Page/View/Tag.php +++ b/CRM/Contact/Page/View/Tag.php @@ -1,9 +1,9 @@ _contactId = CRM_Utils_Request::retrieve('id', 'Positive', $this); - - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); + $userID = CRM_Core_Session::singleton()->getLoggedInContactID(); + + $userChecksum = $this->getUserChecksum(); + $validUser = FALSE; + if ($userChecksum) { + $this->assign('userChecksum', $userChecksum); + $validUser = CRM_Contact_BAO_Contact_Utils::validChecksum($this->_contactId, $userChecksum); + $this->_isChecksumUser = $validUser; + } if (!$this->_contactId) { $this->_contactId = $userID; } - elseif ($this->_contactId != $userID) { + elseif ($this->_contactId != $userID && !$validUser) { if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::VIEW)) { CRM_Core_Error::fatal(ts('You do not have permission to access this contact.')); } @@ -97,7 +101,7 @@ public function preProcess() { $this->set('displayName', $displayName); $this->set('contactImage', $contactImage); - CRM_Utils_System::setTitle(ts('Dashboard - %1', array(1 => $displayName))); + CRM_Utils_System::setTitle(ts('Dashboard - %1', [1 => $displayName])); $this->assign('recentlyViewed', FALSE); } @@ -107,10 +111,9 @@ public function preProcess() { */ public function buildUserDashBoard() { //build component selectors - $dashboardElements = array(); - $config = CRM_Core_Config::singleton(); + $dashboardElements = []; - $this->_userOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + $dashboardOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'user_dashboard_options' ); @@ -122,65 +125,66 @@ public function buildUserDashBoard() { continue; } - if (!empty($this->_userOptions[$name]) && + if (!empty($dashboardOptions[$name]) && (CRM_Core_Permission::access($component->name) || CRM_Core_Permission::check($elem['perm'][0]) ) ) { $userDashboard = $component->getUserDashboardObject(); - $dashboardElements[] = array( + $dashboardElements[] = [ 'class' => 'crm-dashboard-' . strtolower($component->name), 'sectionTitle' => $elem['title'], 'templatePath' => $userDashboard->getTemplateFileName(), 'weight' => $elem['weight'], - ); + ]; $userDashboard->run(); } } // CRM-16512 - Hide related contact table if user lacks permission to view self - if (!empty($this->_userOptions['Permissioned Orgs']) && CRM_Core_Permission::check('view my contact')) { - $dashboardElements[] = array( + if (!empty($dashboardOptions['Permissioned Orgs']) && CRM_Core_Permission::check('view my contact')) { + $columnHeaders = CRM_Contact_BAO_Relationship::getColumnHeaders(); + $contactRelationships = $selector = NULL; + CRM_Utils_Hook::searchColumns('relationship.columns', $columnHeaders, $contactRelationships, $selector); + $this->assign('columnHeaders', $columnHeaders); + $dashboardElements[] = [ 'class' => 'crm-dashboard-permissionedOrgs', 'templatePath' => 'CRM/Contact/Page/View/RelationshipSelector.tpl', 'sectionTitle' => ts('Your Contacts / Organizations'), 'weight' => 40, - ); + ]; } - if (!empty($this->_userOptions['PCP'])) { - $dashboardElements[] = array( + if (!empty($dashboardOptions['PCP'])) { + $dashboardElements[] = [ 'class' => 'crm-dashboard-pcp', 'templatePath' => 'CRM/Contribute/Page/PcpUserDashboard.tpl', 'sectionTitle' => ts('Personal Campaign Pages'), 'weight' => 40, - ); + ]; list($pcpBlock, $pcpInfo) = CRM_PCP_BAO_PCP::getPcpDashboardInfo($this->_contactId); $this->assign('pcpBlock', $pcpBlock); $this->assign('pcpInfo', $pcpInfo); } - if (!empty($this->_userOptions['Assigned Activities'])) { + if (!empty($dashboardOptions['Assigned Activities']) && empty($this->_isChecksumUser)) { // Assigned Activities section - $dashboardElements[] = array( + $dashboardElements[] = [ 'class' => 'crm-dashboard-assignedActivities', 'templatePath' => 'CRM/Activity/Page/UserDashboard.tpl', 'sectionTitle' => ts('Your Assigned Activities'), 'weight' => 5, - ); + ]; $userDashboard = new CRM_Activity_Page_UserDashboard(); $userDashboard->run(); } - usort($dashboardElements, array('CRM_Utils_Sort', 'cmpFunc')); + usort($dashboardElements, ['CRM_Utils_Sort', 'cmpFunc']); $this->assign('dashboardElements', $dashboardElements); - // return true when 'Invoices / Credit Notes' checkbox is checked - $this->assign('invoices', $this->_userOptions['Invoices / Credit Notes']); - - if (!empty($this->_userOptions['Groups'])) { + if (!empty($dashboardOptions['Groups'])) { $this->assign('showGroup', TRUE); //build group selector $gContact = new CRM_Contact_Page_View_UserDashBoard_GroupContact(); @@ -210,32 +214,32 @@ public static function &links() { if (!(self::$_links)) { $disableExtra = ts('Are you sure you want to disable this relationship?'); - self::$_links = array( - CRM_Core_Action::UPDATE => array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit Contact Information'), 'url' => 'civicrm/contact/relatedcontact', 'qs' => 'action=update&reset=1&cid=%%cbid%%&rcid=%%cid%%', - 'title' => ts('Edit Relationship'), - ), - CRM_Core_Action::VIEW => array( + 'title' => ts('Edit Contact Information'), + ], + CRM_Core_Action::VIEW => [ 'name' => ts('Dashboard'), 'url' => 'civicrm/user', 'class' => 'no-popup', 'qs' => 'reset=1&id=%%cbid%%', - 'title' => ts('View Relationship'), - ), - ); + 'title' => ts('View Contact Dashboard'), + ], + ]; if (CRM_Core_Permission::check('access CiviCRM')) { - self::$_links = array_merge(self::$_links, array( - CRM_Core_Action::DISABLE => array( + self::$_links += [ + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'url' => 'civicrm/contact/view/rel', 'qs' => 'action=disable&reset=1&cid=%%cid%%&id=%%id%%&rtype=%%rtype%%&selectedChild=rel&context=dashboard', 'extra' => 'onclick = "return confirm(\'' . $disableExtra . '\');"', 'title' => ts('Disable Relationship'), - ), - )); + ], + ]; } } @@ -248,4 +252,17 @@ public static function &links() { return self::$_links; } + /** + * Get the user checksum from the url to use in links. + * + * @return string + */ + protected function getUserChecksum() { + $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this); + if (empty($userID) && $this->_contactId) { + return $userChecksum; + } + return FALSE; + } + } diff --git a/CRM/Contact/Page/View/UserDashBoard/GroupContact.php b/CRM/Contact/Page/View/UserDashBoard/GroupContact.php index 247d5fe2f074..167618a4c048 100644 --- a/CRM/Contact/Page/View/UserDashBoard/GroupContact.php +++ b/CRM/Contact/Page/View/UserDashBoard/GroupContact.php @@ -1,9 +1,9 @@ _contactId, NULL, NULL, TRUE, TRUE, - $this->_onlyPublicGroups + $this->_onlyPublicGroups, + NULL, NULL, TRUE ); $in = CRM_Contact_BAO_GroupContact::getContactGroup( $this->_contactId, 'Added', NULL, FALSE, TRUE, - $this->_onlyPublicGroups + $this->_onlyPublicGroups, + NULL, NULL, TRUE ); $pending = CRM_Contact_BAO_GroupContact::getContactGroup( $this->_contactId, 'Pending', NULL, FALSE, TRUE, - $this->_onlyPublicGroups + $this->_onlyPublicGroups, + NULL, NULL, TRUE ); $out = CRM_Contact_BAO_GroupContact::getContactGroup( $this->_contactId, 'Removed', NULL, FALSE, TRUE, - $this->_onlyPublicGroups + $this->_onlyPublicGroups, + NULL, NULL, TRUE ); $this->assign('groupCount', $count); diff --git a/CRM/Contact/Page/View/Useradd.php b/CRM/Contact/Page/View/Useradd.php index a7bef2708f12..aa79fc702246 100644 --- a/CRM/Contact/Page/View/Useradd.php +++ b/CRM/Contact/Page/View/Useradd.php @@ -1,9 +1,9 @@ preProcess(); - $params = array(); - $defaults = array(); - $ids = array(); + $params = []; + $defaults = []; + $ids = []; $params['id'] = $params['contact_id'] = $this->_contactId; $contact = CRM_Contact_BAO_Contact::retrieve($params, $defaults, $ids); // now that we have the contact's data - let's build the vCard // TODO: non-US-ASCII support (requires changes to the Contact_Vcard_Build class) - $vcardNames = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array('labelColumn' => 'vcard_name')); + $vcardNames = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', ['labelColumn' => 'vcard_name']); $vcard = new Contact_Vcard_Build('2.1'); if ($defaults['contact_type'] == 'Individual') { diff --git a/CRM/Contact/Selector.php b/CRM/Contact/Selector.php index 9d1ceb8f69c0..4fd277c6ffff 100644 --- a/CRM/Contact/Selector.php +++ b/CRM/Contact/Selector.php @@ -1,9 +1,9 @@ _options = &$this->_query->_options; } + /** + * This method set cache key, later used in test environment + * + * @param string $key + */ + public function setKey($key) { + $this->_key = $key; + } + /** * This method returns the links that are given for each search row. * currently the links added for each row are @@ -254,34 +263,34 @@ public static function &links() { $searchContext = ($context) ? "&context=$context" : NULL; if (!(self::$_links)) { - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('View'), 'url' => 'civicrm/contact/view', 'class' => 'no-popup', 'qs' => "reset=1&cid=%%id%%{$searchContext}{$extraParams}", 'title' => ts('View Contact Details'), 'ref' => 'view-contact', - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/contact/add', 'class' => 'no-popup', 'qs' => "reset=1&action=update&cid=%%id%%{$searchContext}{$extraParams}", 'title' => ts('Edit Contact Details'), 'ref' => 'edit-contact', - ), - ); + ], + ]; $config = CRM_Core_Config::singleton(); //CRM-16552: mapAPIKey is not mandatory as google no longer requires an API Key if ($config->mapProvider && ($config->mapAPIKey || $config->mapProvider == 'Google')) { - self::$_links[CRM_Core_Action::MAP] = array( + self::$_links[CRM_Core_Action::MAP] = [ 'name' => ts('Map'), 'url' => 'civicrm/contact/map', 'qs' => "reset=1&cid=%%id%%{$searchContext}{$extraParams}", 'title' => ts('Map Contact'), - ); + ]; } // Adding Context Menu Links in more action @@ -302,14 +311,14 @@ public static function &links() { $qs = "atype=3&action=add&reset=1&cid=%%id%%{$extraParams}"; } - self::$_links[$counter++] = array( + self::$_links[$counter++] = [ 'name' => $value['title'], 'url' => $url, 'qs' => $qs, 'title' => $value['title'], 'ref' => $value['ref'], 'class' => CRM_Utils_Array::value('class', $value), - ); + ]; } } } @@ -339,7 +348,7 @@ public function getPagerParams($action, &$params) { */ public function &getColHeads($action = NULL, $output = NULL) { $colHeads = self::_getColumnHeaders(); - $colHeads[] = array('desc' => ts('Actions'), 'name' => ts('Action')); + $colHeads[] = ['desc' => ts('Actions'), 'name' => ts('Action')]; return $colHeads; } @@ -360,18 +369,18 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { // unset return property elements that we don't care if (!empty($this->_returnProperties)) { - $doNotCareElements = array( + $doNotCareElements = [ 'contact_type', 'contact_sub_type', 'sort_name', - ); + ]; foreach ($doNotCareElements as $value) { unset($this->_returnProperties[$value]); } } if ($output == CRM_Core_Selector_Controller::EXPORT) { - $csvHeaders = array(ts('Contact ID'), ts('Contact Type')); + $csvHeaders = [ts('Contact ID'), ts('Contact Type')]; foreach ($this->getColHeads($action, $output) as $column) { if (array_key_exists('name', $column)) { $csvHeaders[] = $column['name']; @@ -380,7 +389,7 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { $headers = $csvHeaders; } elseif ($output == CRM_Core_Selector_Controller::SCREEN) { - $csvHeaders = array(ts('Name')); + $csvHeaders = [ts('Name')]; foreach ($this->getColHeads($action, $output) as $key => $column) { if (array_key_exists('name', $column) && $column['name'] && @@ -394,20 +403,20 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { elseif ($this->_ufGroupID) { // we dont use the cached value of column headers // since it potentially changed because of the profile selected - static $skipFields = array('group', 'tag'); + static $skipFields = ['group', 'tag']; $direction = CRM_Utils_Sort::ASCENDING; $empty = TRUE; if (!self::$_columnHeaders) { - self::$_columnHeaders = array( - array('name' => ''), - array( + self::$_columnHeaders = [ + ['name' => ''], + [ 'name' => ts('Name'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - ); + ], + ]; - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); + $locationTypes = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); foreach ($this->_fields as $name => $field) { if (!empty($field['in_selector']) && @@ -423,11 +432,11 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { $locationTypeName = $locationTypes[$lType]; } - if (in_array($fieldName, array( + if (in_array($fieldName, [ 'phone', 'im', 'email', - ))) { + ])) { if ($type) { $name = "`$locationTypeName-$fieldName-$type`"; } @@ -444,11 +453,11 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { $name = 'contact_id'; } - self::$_columnHeaders[] = array( + self::$_columnHeaders[] = [ 'name' => $field['title'], 'sort' => $name, 'direction' => $direction, - ); + ]; $direction = CRM_Utils_Sort::DONTCARE; $empty = FALSE; } @@ -457,23 +466,23 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { // if we dont have any valid columns, dont add the implicit ones // this allows the template to check on emptiness of column headers if ($empty) { - self::$_columnHeaders = array(); + self::$_columnHeaders = []; } else { - self::$_columnHeaders[] = array('desc' => ts('Actions'), 'name' => ts('Action')); + self::$_columnHeaders[] = ['desc' => ts('Actions'), 'name' => ts('Action')]; } } $headers = self::$_columnHeaders; } elseif (!empty($this->_returnProperties)) { - self::$_columnHeaders = array( - array('name' => ''), - array( + self::$_columnHeaders = [ + ['name' => ''], + [ 'name' => ts('Name'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - ); + ], + ]; $properties = self::makeProperties($this->_returnProperties); foreach ($properties as $prop) { @@ -483,7 +492,15 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { if (trim($phoneType) && !is_numeric($phoneType) && strtolower($phoneType) != $fld) { $title .= "-{$phoneType}"; } - $title .= " ($loc)"; + // fetch Location type label from name as $loc, which will be later used in column header + $title .= sprintf(" (%s)", + CRM_Core_PseudoConstant::getLabel( + 'CRM_Core_DAO_Address', + 'location_type_id', + CRM_Core_PseudoConstant::getKey('CRM_Core_DAO_Address', 'location_type_id', $loc) + ) + ); + } elseif (isset($this->_query->_fields[$prop]) && isset($this->_query->_fields[$prop]['title'])) { $title = $this->_query->_fields[$prop]['title']; @@ -495,9 +512,9 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { $title = ''; } - self::$_columnHeaders[] = array('name' => $title, 'sort' => $prop); + self::$_columnHeaders[] = ['name' => $title, 'sort' => $prop]; } - self::$_columnHeaders[] = array('name' => ts('Actions')); + self::$_columnHeaders[] = ['name' => ts('Actions')]; $headers = self::$_columnHeaders; } else { @@ -518,11 +535,11 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { public function getTotalCount($action) { // Use count from cache during paging/sorting if (!empty($_GET['crmPID']) || !empty($_GET['crmSID'])) { - $count = CRM_Core_BAO_Cache::getItem('Search Results Count', $this->_key); + $count = Civi::cache('long')->get("Search Results Count $this->_key"); } if (empty($count)) { $count = $this->_query->searchQuery(0, 0, NULL, TRUE); - CRM_Core_BAO_Cache::setItem($count, 'Search Results Count', $this->_key); + Civi::cache('long')->set("Search Results Count $this->_key", $count); } return $count; } @@ -545,7 +562,6 @@ public function getTotalCount($action) { * the total number of rows for this action */ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { - if (($output == CRM_Core_Selector_Controller::EXPORT || $output == CRM_Core_Selector_Controller::SCREEN ) && @@ -561,16 +577,19 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { // and contain the search criteria (parameters) // note that the default action is basic if ($rowCount) { + /** @var CRM_Core_PrevNextCache_Interface $prevNext */ + $prevNext = Civi::service('prevnext'); $cacheKey = $this->buildPrevNextCache($sort); - $result = $this->_query->getCachedContacts($cacheKey, $offset, $rowCount, $includeContactIds); + $cids = $prevNext->fetch($cacheKey, $offset, $rowCount); + $resultSet = empty($cids) ? [] : $this->_query->getCachedContacts($cids, $includeContactIds)->fetchGenerator(); } else { - $result = $this->_query->searchQuery($offset, $rowCount, $sort, FALSE, $includeContactIds); + $resultSet = $this->_query->searchQuery($offset, $rowCount, $sort, FALSE, $includeContactIds)->fetchGenerator(); } // process the result of the query - $rows = array(); - $permissions = array(CRM_Core_Permission::getPermission()); + $rows = []; + $permissions = [CRM_Core_Permission::getPermission()]; if (CRM_Core_Permission::check('delete contacts')) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -586,8 +605,8 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { if ($this->_ufGroupID) { $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); - $names = array(); - static $skipFields = array('group', 'tag'); + $names = []; + static $skipFields = ['group', 'tag']; foreach ($this->_fields as $key => $field) { if (!empty($field['in_selector']) && !in_array($key, $skipFields) @@ -609,11 +628,11 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { } $locationTypeName = str_replace(' ', '_', $locationTypeName); - if (in_array($fieldName, array( + if (in_array($fieldName, [ 'phone', 'im', 'email', - ))) { + ])) { if ($type) { $names[] = "{$locationTypeName}-{$fieldName}-{$type}"; } @@ -645,17 +664,17 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { //check explicitly added contact to a Smart Group. $groupID = CRM_Utils_Array::value('group', $this->_formValues); - $pseudoconstants = array(); + $pseudoconstants = []; // for CRM-3157 purposes if (in_array('world_region', $names)) { - $pseudoconstants['world_region'] = array( + $pseudoconstants['world_region'] = [ 'dbName' => 'worldregion_id', 'values' => CRM_Core_PseudoConstant::worldRegion(), - ); + ]; } - while ($result->fetch()) { - $row = array(); + foreach ($resultSet as $result) { + $row = []; $this->_query->convertToPseudoNames($result); // the columns we are interested in foreach ($names as $property) { @@ -678,11 +697,11 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row[$property] = $result->$property . " ({$providerName})"; } } - elseif (in_array($property, array( + elseif (in_array($property, [ 'addressee', 'email_greeting', 'postal_greeting', - ))) { + ])) { $greeting = $property . '_display'; $row[$property] = $result->$greeting; } @@ -722,12 +741,12 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { ) { $contactID = $result->contact_id; if ($contactID) { - $gcParams = array( + $gcParams = [ 'contact_id' => $contactID, 'group_id' => $groupID, - ); + ]; - $gcDefaults = array(); + $gcDefaults = []; CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_GroupContact', $gcParams, $gcDefaults); if (empty($gcDefaults)) { @@ -751,33 +770,33 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { if (!empty($this->_formValues['deleted_contacts']) && CRM_Core_Permission::check('access deleted contacts') ) { - $links = array( - array( + $links = [ + [ 'name' => ts('View'), 'url' => 'civicrm/contact/view', 'qs' => 'reset=1&cid=%%id%%', 'class' => 'no-popup', 'title' => ts('View Contact Details'), - ), - array( + ], + [ 'name' => ts('Restore'), 'url' => 'civicrm/contact/view/delete', 'qs' => 'reset=1&cid=%%id%%&restore=1', 'title' => ts('Restore Contact'), - ), - ); + ], + ]; if (CRM_Core_Permission::check('delete contacts')) { - $links[] = array( + $links[] = [ 'name' => ts('Delete Permanently'), 'url' => 'civicrm/contact/view/delete', 'qs' => 'reset=1&cid=%%id%%&skip_undelete=1', 'title' => ts('Permanently Delete Contact'), - ); + ]; } $row['action'] = CRM_Core_Action::formLink( $links, NULL, - array('id' => $result->contact_id), + ['id' => $result->contact_id], ts('more'), FALSE, 'contact.selector.row', @@ -793,7 +812,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['action'] = CRM_Core_Action::formLink( $links, $mask, - array('id' => $result->contact_id), + ['id' => $result->contact_id], ts('more'), FALSE, 'contact.selector.row', @@ -805,7 +824,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['action'] = CRM_Core_Action::formLink( $links, $mapMask, - array('id' => $result->contact_id), + ['id' => $result->contact_id], ts('more'), FALSE, 'contact.selector.row', @@ -823,7 +842,6 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { ); $row['contact_type_orig'] = $result->contact_sub_type ? $result->contact_sub_type : $result->contact_type; - $row['contact_sub_type'] = $result->contact_sub_type ? CRM_Contact_BAO_ContactType::contactTypePairs(FALSE, $result->contact_sub_type, ', ') : $result->contact_sub_type; $row['contact_id'] = $result->contact_id; $row['sort_name'] = $result->sort_name; // Surely this if should be if NOT - otherwise it's just wierd. @@ -864,7 +882,7 @@ public function buildPrevNextCache($sort) { // check for current != previous to ensure cache is not reset if paging is done without changing // sort criteria if (!$pageNum || (!empty($currentSortID) && $currentSortID != $previousSortID)) { - CRM_Core_BAO_PrevNextCache::deleteItem(NULL, $cacheKey, 'civicrm_contact'); + Civi::service('prevnext')->deleteItem(NULL, $cacheKey, 'civicrm_contact'); // this means it's fresh search, so set pageNum=1 if (!$pageNum) { $pageNum = 1; @@ -883,10 +901,9 @@ public function buildPrevNextCache($sort) { $sortByCharacter = CRM_Utils_Request::retrieve('sortByCharacter', 'String'); //for text field pagination selection save - $countRow = CRM_Core_BAO_PrevNextCache::getCount($cacheKey, NULL, "entity_table = 'civicrm_contact'"); + $countRow = Civi::service('prevnext')->getCount($cacheKey); // $sortByCharacter triggers a refresh in the prevNext cache if ($sortByCharacter && $sortByCharacter != 'all') { - $cacheKey .= "_alphabet"; $this->fillupPrevNextCache($sort, $cacheKey, 0, max(self::CACHE_SIZE, $pageSize)); } elseif (($firstRecord + $pageSize) >= $countRow) { @@ -900,7 +917,7 @@ public function buildPrevNextCache($sort) { */ public function addActions(&$rows) { - $basicPermissions = CRM_Core_Permission::check('delete contacts') ? array(CRM_Core_Permission::DELETE) : array(); + $basicPermissions = CRM_Core_Permission::check('delete contacts') ? [CRM_Core_Permission::DELETE] : []; // get permissions on an individual level (CRM-12645) // @todo look at storing this to the session as this is called twice during search results render. @@ -911,10 +928,10 @@ public function addActions(&$rows) { foreach ($rows as $id => & $row) { $links = $links_template; if (in_array($id, $can_edit_list)) { - $mask = CRM_Core_Action::mask(array_merge(array(CRM_Core_Permission::EDIT), $basicPermissions)); + $mask = CRM_Core_Action::mask(array_merge([CRM_Core_Permission::EDIT], $basicPermissions)); } else { - $mask = CRM_Core_Action::mask(array_merge(array(CRM_Core_Permission::VIEW), $basicPermissions)); + $mask = CRM_Core_Action::mask(array_merge([CRM_Core_Permission::VIEW], $basicPermissions)); } if ((!is_numeric(CRM_Utils_Array::value('geo_code_1', $row))) && @@ -927,33 +944,33 @@ public function addActions(&$rows) { if (!empty($this->_formValues['deleted_contacts']) && CRM_Core_Permission::check('access deleted contacts') ) { - $links = array( - array( + $links = [ + [ 'name' => ts('View'), 'url' => 'civicrm/contact/view', 'qs' => 'reset=1&cid=%%id%%', 'class' => 'no-popup', 'title' => ts('View Contact Details'), - ), - array( + ], + [ 'name' => ts('Restore'), 'url' => 'civicrm/contact/view/delete', 'qs' => 'reset=1&cid=%%id%%&restore=1', 'title' => ts('Restore Contact'), - ), - ); + ], + ]; if (CRM_Core_Permission::check('delete contacts')) { - $links[] = array( + $links[] = [ 'name' => ts('Delete Permanently'), 'url' => 'civicrm/contact/view/delete', 'qs' => 'reset=1&cid=%%id%%&skip_undelete=1', 'title' => ts('Permanently Delete Contact'), - ); + ]; } $row['action'] = CRM_Core_Action::formLink( $links, NULL, - array('id' => $row['contact_id']), + ['id' => $row['contact_id']], ts('more'), FALSE, 'contact.selector.actions', @@ -965,7 +982,7 @@ public function addActions(&$rows) { $row['action'] = CRM_Core_Action::formLink( $links, $mask, - array('id' => $row['contact_id']), + ['id' => $row['contact_id']], ts('more'), FALSE, 'contact.selector.actions', @@ -1005,14 +1022,12 @@ public function fillupPrevNextCache($sort, $cacheKey, $start = 0, $end = self::C // For custom searches, use the contactIDs method if (is_a($this, 'CRM_Contact_Selector_Custom')) { $sql = $this->_search->contactIDs($start, $end, $sort, TRUE); - $replaceSQL = "SELECT contact_a.id as contact_id"; $coreSearch = FALSE; } // For core searches use the searchQuery method else { - $sql = $this->_query->searchQuery($start, $end, $sort, FALSE, $this->_query->_includeContactIds, - FALSE, TRUE, TRUE); - $replaceSQL = "SELECT contact_a.id as id"; + $sql = $this->_query->getSearchSQL($start, $end, $sort, FALSE, $this->_query->_includeContactIds, + FALSE, TRUE); } // CRM-9096 @@ -1024,32 +1039,32 @@ public function fillupPrevNextCache($sort, $cacheKey, $start = 0, $end = self::C // the other alternative of running the FULL query will just be incredibly inefficient // and slow things down way too much on large data sets / complex queries - $insertSQL = " -INSERT INTO civicrm_prevnext_cache ( entity_table, entity_id1, entity_id2, cacheKey, data ) -SELECT DISTINCT 'civicrm_contact', contact_a.id, contact_a.id, '$cacheKey', contact_a.display_name -"; - - $sql = str_replace($replaceSQL, $insertSQL, $sql); + $selectSQL = "SELECT DISTINCT %1, contact_a.id, contact_a.sort_name"; - $errorScope = CRM_Core_TemporaryErrorScope::ignoreException(); - $result = CRM_Core_DAO::executeQuery($sql); - unset($errorScope); - - if (is_a($result, 'DB_Error')) { - // check if we get error during core search + $sql = str_ireplace(["SELECT contact_a.id as contact_id", "SELECT contact_a.id as id"], $selectSQL, $sql); + try { + Civi::service('prevnext')->fillWithSql($cacheKey, $sql, [1 => [$cacheKey, 'String']]); + } + catch (CRM_Core_Exception $e) { if ($coreSearch) { // in the case of error, try rebuilding cache using full sql which is used for search selector display // this fixes the bugs reported in CRM-13996 & CRM-14438 $this->rebuildPreNextCache($start, $end, $sort, $cacheKey); } else { - // return if above query fails + // This will always show for CiviRules :-( as a) it orders by 'rule_label' + // which is not available in the query & b) it uses contact not contact_a + // as an alias. + // CRM_Core_Session::setStatus(ts('Query Failed')); return; } } - // also record an entry in the cache key table, so we can delete it periodically - CRM_Core_BAO_Cache::setItem($cacheKey, 'CiviCRM Search PrevNextCache', $cacheKey); + if (Civi::service('prevnext') instanceof CRM_Core_PrevNextCache_Sql) { + // SQL-backed prevnext cache uses an extra record for pruning the cache. + // Also ensure that caches stay alive for 2 days as per previous code + Civi::cache('prevNextCache')->set($cacheKey, $cacheKey, 60 * 60 * 24 * CRM_Core_PrevNextCache_Sql::cacheDays); + } } /** @@ -1071,18 +1086,17 @@ public function rebuildPreNextCache($start, $end, $sort, $cacheKey) { $dao = CRM_Core_DAO::executeQuery($sql); // build insert query, note that currently we build cache for 500 (self::CACHE_SIZE) contact records at a time, hence below approach - $insertValues = array(); + $rows = []; while ($dao->fetch()) { - $insertValues[] = "('civicrm_contact', {$dao->contact_id}, {$dao->contact_id}, '{$cacheKey}', '" . CRM_Core_DAO::escapeString($dao->sort_name) . "')"; + $rows[] = [ + 'entity_table' => 'civicrm_contact', + 'entity_id1' => $dao->contact_id, + 'entity_id2' => $dao->contact_id, + 'data' => $dao->sort_name, + ]; } - //update pre/next cache using single insert query - if (!empty($insertValues)) { - $sql = 'INSERT INTO civicrm_prevnext_cache ( entity_table, entity_id1, entity_id2, cacheKey, data ) VALUES -' . implode(',', $insertValues); - - $result = CRM_Core_DAO::executeQuery($sql); - } + Civi::service('prevnext')->fillWithArray($cacheKey, $rows); } /** @@ -1116,38 +1130,38 @@ private static function &_getColumnHeaders() { 'address_options', TRUE, NULL, TRUE ); - self::$_columnHeaders = array( - 'contact_type' => array('desc' => ts('Contact Type')), - 'sort_name' => array( + self::$_columnHeaders = [ + 'contact_type' => ['desc' => ts('Contact Type')], + 'sort_name' => [ 'name' => ts('Name'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - ); + ], + ]; - $defaultAddress = array( - 'street_address' => array('name' => ts('Address')), - 'city' => array( + $defaultAddress = [ + 'street_address' => ['name' => ts('Address')], + 'city' => [ 'name' => ts('City'), 'sort' => 'city', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - 'state_province' => array( + ], + 'state_province' => [ 'name' => ts('State'), 'sort' => 'state_province', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - 'postal_code' => array( + ], + 'postal_code' => [ 'name' => ts('Postal'), 'sort' => 'postal_code', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - 'country' => array( + ], + 'country' => [ 'name' => ts('Country'), 'sort' => 'country', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - ); + ], + ]; foreach ($defaultAddress as $columnName => $column) { if (!empty($addressOptions[$columnName])) { @@ -1155,13 +1169,13 @@ private static function &_getColumnHeaders() { } } - self::$_columnHeaders['email'] = array( + self::$_columnHeaders['email'] = [ 'name' => ts('Email'), 'sort' => 'email', 'direction' => CRM_Utils_Sort::DONTCARE, - ); + ]; - self::$_columnHeaders['phone'] = array('name' => ts('Phone')); + self::$_columnHeaders['phone'] = ['name' => ts('Phone')]; } return self::$_columnHeaders; } @@ -1177,19 +1191,18 @@ public function getQuery() { * @return CRM_Contact_DAO_Contact */ public function alphabetQuery() { - return $this->_query->searchQuery(NULL, NULL, NULL, FALSE, FALSE, TRUE); + return $this->_query->alphabetQuery(); } /** * @param array $params - * @param $action * @param int $sortID * @param null $displayRelationshipType * @param string $queryOperator * * @return CRM_Contact_DAO_Contact */ - public function contactIDQuery($params, $action, $sortID, $displayRelationshipType = NULL, $queryOperator = 'AND') { + public function contactIDQuery($params, $sortID, $displayRelationshipType = NULL, $queryOperator = 'AND') { $sortOrder = &$this->getSortOrder($this->_action); $sort = new CRM_Utils_Sort($sortOrder, $sortID); @@ -1215,11 +1228,7 @@ public function contactIDQuery($params, $action, $sortID, $displayRelationshipTy $queryOperator ); } - $value = $query->searchQuery(0, 0, $sort, - FALSE, FALSE, FALSE, - FALSE, FALSE - ); - return $value; + return $query->searchQuery(0, 0, $sort); } /** @@ -1228,16 +1237,16 @@ public function contactIDQuery($params, $action, $sortID, $displayRelationshipTy * @return array */ public function &makeProperties(&$returnProperties) { - $properties = array(); + $properties = []; foreach ($returnProperties as $name => $value) { if ($name != 'location') { // special handling for group and tag - if (in_array($name, array('group', 'tag'))) { + if (in_array($name, ['group', 'tag'])) { $name = "{$name}s"; } // special handling for notes - if (in_array($name, array('note', 'note_subject', 'note_body'))) { + if (in_array($name, ['note', 'note_subject', 'note_body'])) { $name = "notes"; } @@ -1248,6 +1257,7 @@ public function &makeProperties(&$returnProperties) { foreach ($value as $n => $v) { foreach ($v as $n1 => $v1) { if (!strpos('_id', $n1) && $n1 != 'location_type') { + $n = str_replace(' ', '_', $n); $properties[] = "{$n}-{$n1}"; } } diff --git a/CRM/Contact/Selector/Controller.php b/CRM/Contact/Selector/Controller.php index 6f2599bf8525..27dc70f75327 100644 --- a/CRM/Contact/Selector/Controller.php +++ b/CRM/Contact/Selector/Controller.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('View'), 'url' => 'civicrm/contact/view', 'qs' => "reset=1&cid=%%id%%{$extraParams}{$searchContext}", 'class' => 'no-popup', 'title' => ts('View Contact Details'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/contact/add', 'qs' => 'reset=1&action=update&cid=%%id%%', 'class' => 'no-popup', 'title' => ts('Edit Contact Details'), - ), - ); + ], + ]; $config = CRM_Core_Config::singleton(); //CRM-16552: mapAPIKey is not mandatory as google no longer requires an API Key if ($config->mapProvider && ($config->mapAPIKey || $config->mapProvider == 'Google')) { - self::$_links[CRM_Core_Action::MAP] = array( + self::$_links[CRM_Core_Action::MAP] = [ 'name' => ts('Map'), 'url' => 'civicrm/contact/map', 'qs' => 'reset=1&cid=%%id%%&searchType=custom', 'class' => 'no-popup', 'title' => ts('Map Contact'), - ); + ]; } } return self::$_links; @@ -227,21 +228,24 @@ public function getPagerParams($action, &$params) { */ public function &getColumnHeaders($action = NULL, $output = NULL) { $columns = $this->_search->columns(); - if ($output == CRM_Core_Selector_Controller::EXPORT) { - return array_keys($columns); + $headers = []; + if ($output == CRM_Core_Selector_Controller::EXPORT || $output == CRM_Core_Selector_Controller::SCREEN) { + foreach ($columns as $name => $key) { + $headers[$key] = $name; + } + return $headers; } else { - $headers = array(); foreach ($columns as $name => $key) { if (!empty($name)) { - $headers[] = array( + $headers[] = [ 'name' => $name, 'sort' => $key, 'direction' => CRM_Utils_Sort::ASCENDING, - ); + ]; } else { - $headers[] = array(); + $headers[] = []; } } return $headers; @@ -297,13 +301,13 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $contactQueryObj = $this->_search->getQueryObj(); } - $dao = CRM_Core_DAO::executeQuery($sql, CRM_Core_DAO::$_nullArray); + $dao = CRM_Core_DAO::executeQuery($sql); $columns = $this->_search->columns(); $columnNames = array_values($columns); $links = self::links($this->_key); - $permissions = array(CRM_Core_Permission::getPermission()); + $permissions = [CRM_Core_Permission::getPermission()]; if (CRM_Core_Permission::check('delete contacts')) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -320,9 +324,9 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $image = TRUE; } // process the result of the query - $rows = array(); + $rows = []; while ($dao->fetch()) { - $row = array(); + $row = []; $empty = TRUE; // if contact query object present @@ -346,7 +350,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['checkbox'] = CRM_Core_Form::CB_PREFIX . $contactID; $row['action'] = CRM_Core_Action::formLink($links, $mask, - array('id' => $contactID), + ['id' => $contactID], ts('more'), FALSE, 'contact.custom.actions', @@ -424,12 +428,12 @@ public function alphabetQuery() { * * @return Object */ - public function contactIDQuery($params, $action, $sortID, $displayRelationshipType = NULL, $queryOperator = 'AND') { + public function contactIDQuery($params, $sortID, $displayRelationshipType = NULL, $queryOperator = 'AND') { // $action, $displayRelationshipType and $queryOperator are unused. I have // no idea why they are there. // I wonder whether there is some helper function for this: - $matches = array(); + $matches = []; if (preg_match('/([0-9]*)(_(u|d))?/', $sortID, $matches)) { $columns = array_values($this->_search->columns()); $sort = $columns[$matches[1] - 1]; @@ -453,7 +457,7 @@ public function contactIDQuery($params, $action, $sortID, $displayRelationshipTy public function addActions(&$rows) { $links = self::links($this->_key); - $permissions = array(CRM_Core_Permission::getPermission()); + $permissions = [CRM_Core_Permission::getPermission()]; if (CRM_Core_Permission::check('delete contacts')) { $permissions[] = CRM_Core_Permission::DELETE; } @@ -462,7 +466,7 @@ public function addActions(&$rows) { foreach ($rows as $id => & $row) { $row['action'] = CRM_Core_Action::formLink($links, $mask, - array('id' => $row['contact_id']), + ['id' => $row['contact_id']], ts('more'), FALSE, 'contact.custom.actions', diff --git a/CRM/Contact/StateMachine/Search.php b/CRM/Contact/StateMachine/Search.php index 61bed6a48293..97d8b375cd20 100644 --- a/CRM/Contact/StateMachine/Search.php +++ b/CRM/Contact/StateMachine/Search.php @@ -1,9 +1,9 @@ _pages = array(); + $this->_pages = []; if ($action == CRM_Core_Action::ADVANCED) { $this->_pages['CRM_Contact_Form_Search_Advanced'] = NULL; list($task, $result) = $this->taskName($controller, 'Advanced'); @@ -92,7 +92,7 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { * * @param string $formName * - * @return string + * @return array * the name of the form that will handle the task */ public function taskName($controller, $formName = 'Search') { @@ -103,15 +103,10 @@ public function taskName($controller, $formName = 'Search') { } $this->_controller->set('task', $value); - if ($value) { - $componentMode = $this->_controller->get('component_mode'); - $modeValue = CRM_Contact_Form_Search::getModeValue($componentMode); - $taskClassName = $modeValue['taskClassName']; - return $taskClassName::getTask($value); - } - else { - return CRM_Contact_Task::getTask($value); - } + $componentMode = $this->_controller->get('component_mode'); + $modeValue = CRM_Contact_Form_Search::getModeValue($componentMode); + $taskClassName = $modeValue['taskClassName']; + return $taskClassName::getTask($value); } /** diff --git a/CRM/Contact/Task.php b/CRM/Contact/Task.php index fb77c32949a7..97c62e6b17bc 100644 --- a/CRM/Contact/Task.php +++ b/CRM/Contact/Task.php @@ -1,9 +1,9 @@ array( + self::GROUP_ADD => array( 'title' => ts('Group - add contacts'), 'class' => 'CRM_Contact_Form_Task_AddToGroup', + 'url' => 'civicrm/task/add-to-group', ), - self::REMOVE_CONTACTS => array( + self::GROUP_REMOVE => array( 'title' => ts('Group - remove contacts'), 'class' => 'CRM_Contact_Form_Task_RemoveFromGroup', + 'url' => 'civicrm/task/remove-from-group', ), - self::TAG_CONTACTS => array( + self::TAG_ADD => array( 'title' => ts('Tag - add to contacts'), 'class' => 'CRM_Contact_Form_Task_AddToTag', + 'url' => 'civicrm/task/add-to-tag', ), - self::REMOVE_TAGS => array( + self::TAG_REMOVE => array( 'title' => ts('Tag - remove from contacts'), 'class' => 'CRM_Contact_Form_Task_RemoveFromTag', + 'url' => 'civicrm/task/remove-from-tag', ), - self::EXPORT_CONTACTS => array( + self::TASK_EXPORT => array( 'title' => ts('Export contacts'), 'class' => array( 'CRM_Export_Form_Select', @@ -104,15 +88,20 @@ public static function initTasks() { ), 'result' => FALSE, ), - self::EMAIL_CONTACTS => array( - 'title' => ts('Email - send now (to 50 or less)'), + self::TASK_EMAIL => array( + 'title' => ts('Email - send now (to %1 or less)', array( + 1 => Civi::settings() + ->get('simple_mail_limit'), + )), 'class' => 'CRM_Contact_Form_Task_Email', 'result' => TRUE, + 'url' => 'civicrm/task/send-email', ), - self::DELETE_CONTACTS => array( + self::TASK_DELETE => array( 'title' => ts('Delete contacts'), 'class' => 'CRM_Contact_Form_Task_Delete', 'result' => FALSE, + 'url' => 'civicrm/task/delete-contact', ), self::RECORD_CONTACTS => array( 'title' => ts('Add activity'), @@ -128,7 +117,7 @@ public static function initTasks() { 'class' => 'CRM_Contact_Form_Task_SaveSearch_Update', 'result' => TRUE, ), - self::PRINT_CONTACTS => array( + self::TASK_PRINT => array( 'title' => ts('Print selected rows'), 'class' => 'CRM_Contact_Form_Task_Print', 'result' => FALSE, @@ -137,6 +126,7 @@ public static function initTasks() { 'title' => ts('Mailing labels - print'), 'class' => 'CRM_Contact_Form_Task_Label', 'result' => TRUE, + 'url' => 'civicrm/task/make-mailing-label', ), self::BATCH_UPDATE => array( 'title' => ts('Update multiple contacts'), @@ -145,19 +135,23 @@ public static function initTasks() { 'CRM_Contact_Form_Task_Batch', ), 'result' => TRUE, + 'url' => 'civicrm/task/pick-profile', ), - self::PRINT_FOR_CONTACTS => array( + self::PDF_LETTER => array( 'title' => ts('Print/merge document'), 'class' => 'CRM_Contact_Form_Task_PDF', 'result' => TRUE, + 'url' => 'civicrm/task/print-document', ), self::EMAIL_UNHOLD => array( 'title' => ts('Email - unhold addresses'), 'class' => 'CRM_Contact_Form_Task_Unhold', + 'url' => 'civicrm/task/unhold-email', ), self::COMMUNICATION_PREFS => array( 'title' => ts('Communication preferences - alter'), 'class' => 'CRM_Contact_Form_Task_AlterPreferences', + 'url' => 'civicrm/task/alter-contact-preference', ), self::RESTORE => array( 'title' => ts('Restore contacts from trash'), @@ -173,8 +167,8 @@ public static function initTasks() { //CRM-16329, if SMS provider is configured show sms action. $providersCount = CRM_SMS_BAO_Provider::activeProviderCount(); - if ($providersCount) { - self::$_tasks[self::SMS_CONTACTS] = array( + if ($providersCount && CRM_Core_Permission::check('send SMS')) { + self::$_tasks[self::TASK_SMS] = array( 'title' => ts('SMS - schedule/send'), 'class' => 'CRM_Contact_Form_Task_SMS', 'result' => TRUE, @@ -221,7 +215,7 @@ public static function initTasks() { //CRM-4418, check for delete if (!CRM_Core_Permission::check('delete contacts')) { - unset(self::$_tasks[self::DELETE_CONTACTS]); + unset(self::$_tasks[self::TASK_DELETE]); } //show map action only if map provider and geoprovider are set (Google doesn't need geoprovider) @@ -259,43 +253,18 @@ public static function initTasks() { ); } - self::$_tasks += CRM_Core_Component::taskList(); - - CRM_Utils_Hook::searchTasks('contact', self::$_tasks); - - } - } - - /** - * These tasks are the core set of tasks that the user can perform - * on a contact / group of contacts - * - * @return array - * the set of tasks for a group of contacts - */ - public static function &taskTitles() { - self::initTasks(); - - $titles = array(); - foreach (self::$_tasks as $id => $value) { - $titles[$id] = $value['title']; - } - - // hack unset update saved search - unset($titles[self::SAVE_SEARCH_UPDATE]); + if (CRM_Core_Permission::access('CiviCase')) { + self::$_tasks[self::ADD_TO_CASE] = array( + 'title' => 'Add to case as role', + 'class' => 'CRM_Case_Form_AddToCaseAsRole', + 'result' => FALSE, + ); + } - if (!CRM_Utils_Mail::validOutBoundMail()) { - unset($titles[self::EMAIL_CONTACTS]); - unset($titles[self::CREATE_MAILING]); + parent::tasks(); } - // CRM-6806 - if (!CRM_Core_Permission::check('access deleted contacts') || - !CRM_Core_Permission::check('delete contacts') - ) { - unset($titles[self::DELETE_PERMANENTLY]); - } - return $titles; + return self::$_tasks; } /** @@ -303,16 +272,19 @@ public static function &taskTitles() { * of the user * * @param int $permission - * @param bool $deletedContacts - * Are these tasks for operating on deleted contacts?. + * @param array $params + * bool deletedContacts: Are these tasks for operating on deleted contacts?. * * @return array * set of tasks that are valid for the user */ - public static function &permissionedTaskTitles($permission, $deletedContacts = FALSE) { - self::initTasks(); + public static function permissionedTaskTitles($permission, $params = array()) { + if (!isset($params['deletedContacts'])) { + $params['deletedContacts'] = FALSE; + } + self::tasks(); $tasks = array(); - if ($deletedContacts) { + if ($params['deletedContacts']) { if (CRM_Core_Permission::check('access deleted contacts')) { $tasks[self::RESTORE] = self::$_tasks[self::RESTORE]['title']; if (CRM_Core_Permission::check('delete contacts')) { @@ -325,36 +297,25 @@ public static function &permissionedTaskTitles($permission, $deletedContacts = F } else { $tasks = array( - self::EXPORT_CONTACTS => self::$_tasks[self::EXPORT_CONTACTS]['title'], - self::EMAIL_CONTACTS => self::$_tasks[self::EMAIL_CONTACTS]['title'], + self::TASK_EXPORT => self::$_tasks[self::TASK_EXPORT]['title'], + self::TASK_EMAIL => self::$_tasks[self::TASK_EMAIL]['title'], self::LABEL_CONTACTS => self::$_tasks[self::LABEL_CONTACTS]['title'], ); - if (isset(self::$_tasks[self::MAP_CONTACTS]) && - !empty(self::$_tasks[self::MAP_CONTACTS]['title']) - ) { - $tasks[self::MAP_CONTACTS] = self::$_tasks[self::MAP_CONTACTS]['title']; - } - - if (isset(self::$_tasks[self::CREATE_MAILING]) && - !empty(self::$_tasks[self::CREATE_MAILING]['title']) - ) { - $tasks[self::CREATE_MAILING] = self::$_tasks[self::CREATE_MAILING]['title']; + foreach ([ + self::MAP_CONTACTS, + self::CREATE_MAILING, + self::TASK_SMS, + ] as $task) { + if (isset(self::$_tasks[$task]) && + !empty(self::$_tasks[$task]['title']) + ) { + $tasks[$task] = self::$_tasks[$task]['title']; + } } } - return $tasks; - } - /** - * These tasks get added based on the context the user is in. - * - * @return array - * the set of optional tasks for a group of contacts - */ - public static function &optionalTaskTitle() { - $tasks = array( - self::SAVE_SEARCH_UPDATE => self::$_tasks[self::SAVE_SEARCH_UPDATE]['title'], - ); + $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params); return $tasks; } @@ -364,16 +325,13 @@ public static function &optionalTaskTitle() { * @return array */ public static function getTask($value) { - self::initTasks(); + self::tasks(); if (!CRM_Utils_Array::value($value, self::$_tasks)) { // make it the print task by default - $value = self::PRINT_CONTACTS; + $value = self::TASK_PRINT; } - return array( - CRM_Utils_Array::value('class', self::$_tasks[$value]), - CRM_Utils_Array::value('result', self::$_tasks[$value]), - ); + return parent::getTask($value); } } diff --git a/CRM/Contribute/ActionMapping/ByPage.php b/CRM/Contribute/ActionMapping/ByPage.php index 0e3247013e11..acfdea9d6aad 100644 --- a/CRM/Contribute/ActionMapping/ByPage.php +++ b/CRM/Contribute/ActionMapping/ByPage.php @@ -1,9 +1,9 @@ string $fieldLabel). */ public function getDateFields() { - return array( + return [ 'receive_date' => ts('Receive Date'), 'cancel_date' => ts('Cancel Date'), 'receipt_date' => ts('Receipt Date'), 'thankyou_date' => ts('Thank You Date'), - ); + ]; } /** @@ -146,7 +145,7 @@ public function getDateFields() { * Ex: array('assignee' => 'Activity Assignee'). */ public function getRecipientTypes() { - return array(); + return []; } /** @@ -163,7 +162,7 @@ public function getRecipientTypes() { * @see getRecipientTypes */ public function getRecipientListing($recipientType) { - return array(); + return []; } /** @@ -176,7 +175,7 @@ public function getRecipientListing($recipientType) { * List of error messages. */ public function validateSchedule($schedule) { - return array(); + return []; } /** @@ -221,4 +220,16 @@ public function createQuery($schedule, $phase, $defaultParams) { return $query; } + /** + * Determine whether a schedule based on this mapping should + * reset the reminder state if the trigger date changes. + * + * @return bool + * + * @param \CRM_Core_DAO_ActionSchedule $schedule + */ + public function resetOnTriggerDateChange($schedule) { + return FALSE; + } + } diff --git a/CRM/Contribute/ActionMapping/ByType.php b/CRM/Contribute/ActionMapping/ByType.php index 413375a19cb5..e67b22956fa5 100644 --- a/CRM/Contribute/ActionMapping/ByType.php +++ b/CRM/Contribute/ActionMapping/ByType.php @@ -1,9 +1,9 @@ string $fieldLabel). */ public function getDateFields() { - return array( + return [ 'receive_date' => ts('Receive Date'), 'cancel_date' => ts('Cancel Date'), 'receipt_date' => ts('Receipt Date'), 'thankyou_date' => ts('Thank You Date'), - ); + ]; } /** @@ -146,9 +145,9 @@ public function getDateFields() { * Ex: array('assignee' => 'Activity Assignee'). */ public function getRecipientTypes() { - return array( + return [ 'soft_credit_type' => ts('Soft Credit Role'), - ); + ]; } /** @@ -170,7 +169,7 @@ public function getRecipientListing($recipientType) { return \CRM_Core_OptionGroup::values('soft_credit_type', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'); default: - return array(); + return []; } } @@ -184,7 +183,7 @@ public function getRecipientListing($recipientType) { * List of error messages. */ public function validateSchedule($schedule) { - return array(); + return []; } /** @@ -240,4 +239,16 @@ public function createQuery($schedule, $phase, $defaultParams) { return $query; } + /** + * Determine whether a schedule based on this mapping should + * reset the reminder state if the trigger date changes. + * + * @return bool + * + * @param \CRM_Core_DAO_ActionSchedule $schedule + */ + public function resetOnTriggerDateChange($schedule) { + return FALSE; + } + } diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 37518424d982..e71e7fa4abd2 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -1,9 +1,9 @@ push(CRM_Core_Error::DUPLICATE_CONTRIBUTION, - 'Fatal', - array($d), - "Duplicate error - existing contribution record(s) have a matching Transaction ID or Invoice ID. Contribution record ID(s) are: $d" - ); - return $error; + $message = ts("Duplicate error - existing contribution record(s) have a matching Transaction ID or Invoice ID. Contribution record ID(s) are: %1", [1 => implode(', ', $duplicates)]); + throw new CRM_Core_Exception($message); } // first clean up all the money fields - $moneyFields = array( + $moneyFields = [ 'total_amount', 'net_amount', 'fee_amount', 'non_deductible_amount', - ); + ]; //if priceset is used, no need to cleanup money if (!empty($params['skipCleanMoney'])) { - unset($moneyFields[0]); + $moneyFields = []; + } + else { + // @todo put a deprecated here - this should be done in the form layer. + $params['skipCleanMoney'] = FALSE; + Civi::log()->warning('Deprecated code path. Money should always be clean before it hits the BAO.', array('civi.tag' => 'deprecated')); } foreach ($moneyFields as $field) { @@ -139,29 +141,34 @@ public static function add(&$params, $ids = array()) { //set defaults in create mode if (!$contributionID) { CRM_Core_DAO::setCreateDefaults($params, self::getDefaults()); + + if (empty($params['invoice_number'])) { + $nextContributionID = CRM_Core_DAO::singleValueQuery("SELECT COALESCE(MAX(id) + 1, 1) FROM civicrm_contribution"); + $params['invoice_number'] = self::getInvoiceNumber($nextContributionID); + } } $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); //if contribution is created with cancelled or refunded status, add credit note id - if (!empty($params['contribution_status_id'])) { - // @todo - should we include Chargeback? If so use self::isContributionStatusNegative($params['contribution_status_id']) - if (($params['contribution_status_id'] == array_search('Refunded', $contributionStatus) - || $params['contribution_status_id'] == array_search('Cancelled', $contributionStatus)) - ) { - if (empty($params['creditnote_id']) || $params['creditnote_id'] == "null") { - $params['creditnote_id'] = self::createCreditNoteId(); - } + // do the same for chargeback - this entered the code 'accidentally' but moving it to here + // as part of cleanup maintains consistency. + if (self::isContributionStatusNegative(CRM_Utils_Array::value('contribution_status_id', $params))) { + if (empty($params['creditnote_id'])) { + $params['creditnote_id'] = self::createCreditNoteId(); } } - else { + if (empty($params['contribution_status_id'])) { // Since the fee amount is expecting this (later on) ensure it is always set. // It would only not be set for an update where it is unchanged. - $params['contribution_status_id'] = civicrm_api3('Contribution', 'getvalue', array('id' => $contributionID, 'return' => 'contribution_status_id')); + $params['contribution_status_id'] = civicrm_api3('Contribution', 'getvalue', [ + 'id' => $contributionID, + 'return' => 'contribution_status_id', + ]); } if (!$contributionID && CRM_Utils_Array::value('membership_id', $params) - && self::checkContributeSettings('deferred_revenue_enabled') + && Civi::settings()->get('deferred_revenue_enabled') ) { $memberStartDate = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $params['membership_id'], 'start_date'); if ($memberStartDate) { @@ -179,9 +186,9 @@ public static function add(&$params, $ids = array()) { $setPrevContribution = TRUE; // CRM-13964 partial payment - if (!empty($params['partial_payment_total']) && !empty($params['partial_amount_pay'])) { + if (!empty($params['partial_payment_total']) && !empty($params['partial_amount_to_pay'])) { $partialAmtTotal = $params['partial_payment_total']; - $partialAmtPay = $params['partial_amount_pay']; + $partialAmtPay = $params['partial_amount_to_pay']; $params['total_amount'] = $partialAmtTotal; if ($partialAmtPay < $partialAmtTotal) { $params['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially paid'); @@ -231,7 +238,16 @@ public static function add(&$params, $ids = array()) { //add Account details $params['contribution'] = $contribution; - self::recordFinancialAccounts($params); + if (empty($params['is_post_payment_create'])) { + // If this is being called from the Payment.create api/ BAO then that Entity + // takes responsibility for the financial transactions. In fact calling Payment.create + // to add payments & having it call completetransaction and / or contribution.create + // to update related entities is the preferred flow. + // Note that leveraging this parameter for any other code flow is not supported and + // is likely to break in future and / or cause serious problems in your data. + // https://github.com/civicrm/civicrm-core/pull/14673 + self::recordFinancialAccounts($params); + } if (self::isUpdateToRecurringContribution($params)) { CRM_Contribute_BAO_ContributionRecur::updateOnNewPayment( @@ -282,15 +298,17 @@ public static function isUpdateToRecurringContribution($params) { /** * Get defaults for new entity. + * * @return array */ public static function getDefaults() { - return array( + return [ 'payment_instrument_id' => key(CRM_Core_OptionGroup::values('payment_instrument', - FALSE, FALSE, FALSE, 'AND is_default = 1') + FALSE, FALSE, FALSE, 'AND is_default = 1') ), - 'contribution_status_id' => CRM_Core_Pseudoconstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'), - ); + 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'), + 'receive_date' => date('Y-m-d H:i:s'), + ]; } /** @@ -306,7 +324,7 @@ public static function getDefaults() { * @return CRM_Contribute_BAO_Contribution|null * The found object or null */ - public static function getValues($params, &$values, &$ids) { + public static function getValues($params, &$values = [], &$ids = []) { if (empty($params)) { return NULL; } @@ -321,7 +339,8 @@ public static function getValues($params, &$values, &$ids) { return $contribution; } - $null = NULL; // return by reference + // return by reference + $null = NULL; return $null; } @@ -342,7 +361,7 @@ public static function getValues($params, &$values, &$ids) { * @throws CRM_Core_Exception */ public static function getValuesWithMappings($params) { - $values = $ids = array(); + $values = $ids = []; $contribution = self::getValues($params, $values, $ids); if (is_null($contribution)) { throw new CRM_Core_Exception('No contribution found'); @@ -381,12 +400,12 @@ public static function calculateMissingAmountParams(&$params, $contributionID) { if (isset($params['fee_amount']) || isset($params['total_amount'])) { // We have an existing contribution and fee_amount or total_amount has been passed in but not net_amount. // net_amount may need adjusting. - $contribution = civicrm_api3('Contribution', 'getsingle', array( + $contribution = civicrm_api3('Contribution', 'getsingle', [ 'id' => $contributionID, - 'return' => array('total_amount', 'net_amount'), - )); - $totalAmount = isset($params['total_amount']) ? $params['total_amount'] : CRM_Utils_Array::value('total_amount', $contribution); - $feeAmount = isset($params['fee_amount']) ? $params['fee_amount'] : CRM_Utils_Array::value('fee_amount', $contribution); + 'return' => ['total_amount', 'net_amount', 'fee_amount'], + ]); + $totalAmount = (isset($params['total_amount']) ? (float) $params['total_amount'] : (float) CRM_Utils_Array::value('total_amount', $contribution)); + $feeAmount = (isset($params['fee_amount']) ? (float) $params['fee_amount'] : (float) CRM_Utils_Array::value('fee_amount', $contribution)); $params['net_amount'] = $totalAmount - $feeAmount; } } @@ -401,16 +420,16 @@ public static function calculateMissingAmountParams(&$params, $contributionID) { */ protected static function getBillingAddressParams($params, $billingLocationTypeID) { $hasBillingField = FALSE; - $billingFields = array( + $billingFields = [ 'street_address', 'city', 'state_province_id', 'postal_code', 'country_id', - ); + ]; //build address array - $addressParams = array(); + $addressParams = []; $addressParams['location_type_id'] = $billingLocationTypeID; $addressParams['is_billing'] = 1; @@ -425,7 +444,7 @@ protected static function getBillingAddressParams($params, $billingLocationTypeI $hasBillingField = TRUE; } } - return array($hasBillingField, $addressParams); + return [$hasBillingField, $addressParams]; } /** @@ -446,7 +465,7 @@ public static function getPaymentProcessorReadyAddressParams($params, $billingLo $addressParams[substr($name, 9)] = $addressParams[$field]; } } - return array($hasBillingField, $addressParams); + return [$hasBillingField, $addressParams]; } /** @@ -466,7 +485,7 @@ public function getNumTermsByContributionAndMembershipType($membershipTypeID, $c SELECT membership_num_terms FROM civicrm_line_item li LEFT JOIN civicrm_price_field_value v ON li.price_field_value_id = v.id WHERE contribution_id = %1 AND membership_type_id = %2", - array(1 => array($contributionID, 'Integer'), 2 => array($membershipTypeID, 'Integer')) + [1 => [$contributionID, 'Integer'], 2 => [$membershipTypeID, 'Integer']] ); // default of 1 is precautionary return empty($numTerms) ? 1 : $numTerms; @@ -482,34 +501,31 @@ public function getNumTermsByContributionAndMembershipType($membershipTypeID, $c * * @return CRM_Contribute_BAO_Contribution */ - public static function create(&$params, $ids = array()) { - $dateFields = array('receive_date', 'cancel_date', 'receipt_date', 'thankyou_date', 'revenue_recognition_date'); + public static function create(&$params, $ids = []) { + $contributionID = CRM_Utils_Array::value('contribution', $ids, CRM_Utils_Array::value('id', $params)); + $action = $contributionID ? 'edit' : 'create'; + + $dateFields = [ + 'receive_date', + 'cancel_date', + 'receipt_date', + 'thankyou_date', + 'revenue_recognition_date', + ]; foreach ($dateFields as $df) { if (isset($params[$df])) { $params[$df] = CRM_Utils_Date::isoToMysql($params[$df]); } } - //if contribution is created with cancelled or refunded status, add credit note id - if (!empty($params['contribution_status_id'])) { - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - - if (($params['contribution_status_id'] == array_search('Refunded', $contributionStatus) - || $params['contribution_status_id'] == array_search('Cancelled', $contributionStatus)) - ) { - if (empty($params['creditnote_id']) || $params['creditnote_id'] == "null") { - $params['creditnote_id'] = self::createCreditNoteId(); - } - } - } - $transaction = new CRM_Core_Transaction(); - $contribution = self::add($params, $ids); - - if (is_a($contribution, 'CRM_Core_Error')) { + try { + $contribution = self::add($params, $ids); + } + catch (CRM_Core_Exception $e) { $transaction->rollback(); - return $contribution; + throw $e; } $params['contribution_id'] = $contribution->id; @@ -517,19 +533,19 @@ public static function create(&$params, $ids = array()) { if (!empty($params['custom']) && is_array($params['custom']) ) { - CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id); + CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id, $action); } $session = CRM_Core_Session::singleton(); if (!empty($params['note'])) { - $noteParams = array( + $noteParams = [ 'entity_table' => 'civicrm_contribution', 'note' => $params['note'], 'entity_id' => $contribution->id, 'contact_id' => $session->get('userID'), 'modified_date' => date('Ymd'), - ); + ]; if (!$noteParams['contact_id']) { $noteParams['contact_id'] = $params['contact_id']; } @@ -539,12 +555,12 @@ public static function create(&$params, $ids = array()) { // make entry in batch entity batch table if (!empty($params['batch_id'])) { // in some update cases we need to get extra fields - ie an update that doesn't pass in all these params - $titleFields = array( + $titleFields = [ 'contact_id', 'total_amount', 'currency', 'financial_type_id', - ); + ]; $retrieveRequired = 0; foreach ($titleFields as $titleField) { if (!isset($contribution->$titleField)) { @@ -561,13 +577,13 @@ public static function create(&$params, $ids = array()) { $transaction->commit(); - $activity = civicrm_api3('Activity', 'get', array( + $activity = civicrm_api3('Activity', 'get', [ 'source_record_id' => $contribution->id, - 'options' => array('limit' => 1), + 'options' => ['limit' => 1], 'sequential' => 1, 'activity_type_id' => 'Contribution', - 'return' => array('id', 'campaign'), - )); + 'return' => ['id', 'campaign'], + ]); //CRM-18406: Update activity when edit contribution. if ($activity['count']) { @@ -588,12 +604,12 @@ public static function create(&$params, $ids = array()) { "action=view&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home" ); // in some update cases we need to get extra fields - ie an update that doesn't pass in all these params - $titleFields = array( + $titleFields = [ 'contact_id', 'total_amount', 'currency', 'financial_type_id', - ); + ]; $retrieveRequired = 0; foreach ($titleFields as $titleField) { if (!isset($contribution->$titleField)) { @@ -604,10 +620,10 @@ public static function create(&$params, $ids = array()) { if ($retrieveRequired == 1) { $contribution->find(TRUE); } - $contributionTypes = CRM_Contribute_PseudoConstant::financialType(); - $title = CRM_Contact_BAO_Contact::displayName($contribution->contact_id) . ' - (' . CRM_Utils_Money::format($contribution->total_amount, $contribution->currency) . ' ' . ' - ' . $contributionTypes[$contribution->financial_type_id] . ')'; + $financialType = CRM_Contribute_PseudoConstant::financialType($contribution->financial_type_id); + $title = CRM_Contact_BAO_Contact::displayName($contribution->contact_id) . ' - (' . CRM_Utils_Money::format($contribution->total_amount, $contribution->currency) . ' ' . ' - ' . $financialType . ')'; - $recentOther = array(); + $recentOther = []; if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::UPDATE)) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution', "action=update&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home" @@ -645,7 +661,7 @@ public static function create(&$params, $ids = array()) { public static function resolveDefaults(&$defaults, $reverse = FALSE) { self::lookupValue($defaults, 'financial_type', CRM_Contribute_PseudoConstant::financialType(), $reverse); self::lookupValue($defaults, 'payment_instrument', CRM_Contribute_PseudoConstant::paymentInstrument(), $reverse); - self::lookupValue($defaults, 'contribution_status', CRM_Contribute_PseudoConstant::contributionStatus(), $reverse); + self::lookupValue($defaults, 'contribution_status', CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'label'), $reverse); self::lookupValue($defaults, 'pcp', CRM_Contribute_PseudoConstant::pcPage(), $reverse); } @@ -699,7 +715,7 @@ public static function lookupValue(&$defaults, $property, &$lookup, $reverse) { * * @return CRM_Contribute_BAO_Contribution */ - public static function retrieve(&$params, &$defaults, &$ids) { + public static function retrieve(&$params, &$defaults = [], &$ids = []) { $contribution = CRM_Contribute_BAO_Contribution::getValues($params, $defaults, $ids); return $contribution; } @@ -720,14 +736,14 @@ public static function retrieve(&$params, &$defaults, &$ids) { public static function &importableFields($contactType = 'Individual', $status = TRUE) { if (!self::$_importableFields) { if (!self::$_importableFields) { - self::$_importableFields = array(); + self::$_importableFields = []; } if (!$status) { - $fields = array('' => array('title' => ts('- do not import -'))); + $fields = ['' => ['title' => ts('- do not import -')]]; } else { - $fields = array('' => array('title' => ts('- Contribution Fields -'))); + $fields = ['' => ['title' => ts('- Contribution Fields -')]]; } $note = CRM_Core_DAO_Note::import(); @@ -737,12 +753,12 @@ public static function &importableFields($contactType = 'Individual', $status = $contactFields = CRM_Contact_BAO_Contact::importableFields($contactType, NULL); // Using new Dedupe rule. - $ruleParams = array( + $ruleParams = [ 'contact_type' => $contactType, 'used' => 'Unsupervised', - ); + ]; $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams); - $tmpContactField = array(); + $tmpContactField = []; if (is_array($fieldsArray)) { foreach ($fieldsArray as $value) { //skip if there is no dupe rule @@ -780,15 +796,6 @@ public static function &importableFields($contactType = 'Individual', $status = return self::$_importableFields; } - /** - * Get exportable fields with pseudoconstants rendered as an extra field. - */ - public static function getExportableFieldsWithPseudoConstants() { - $fields = self::exportableFields(); - CRM_Core_DAO::appendPseudoConstantsToFields($fields); - return $fields; - } - /** * Combine all the exportable fields from the lower level objects. * @@ -800,92 +807,96 @@ public static function getExportableFieldsWithPseudoConstants() { public static function &exportableFields($checkPermission = TRUE) { if (!self::$_exportableFields) { if (!self::$_exportableFields) { - self::$_exportableFields = array(); + self::$_exportableFields = []; + } + + $fields = CRM_Contribute_DAO_Contribution::export(); + if (CRM_Contribute_BAO_Query::isSiteHasProducts()) { + $fields = array_merge( + $fields, + CRM_Contribute_DAO_Product::export(), + CRM_Contribute_DAO_ContributionProduct::export(), + // CRM-16713 - contribution search by Premiums on 'Find Contribution' form. + [ + 'contribution_product_id' => [ + 'title' => ts('Premium'), + 'name' => 'contribution_product_id', + 'where' => 'civicrm_product.id', + 'data_type' => CRM_Utils_Type::T_INT, + ], + ] + ); } - $impFields = CRM_Contribute_DAO_Contribution::export(); - $expFieldProduct = CRM_Contribute_DAO_Product::export(); - $expFieldsContrib = CRM_Contribute_DAO_ContributionProduct::export(); - $typeField = CRM_Financial_DAO_FinancialType::export(); $financialAccount = CRM_Financial_DAO_FinancialAccount::export(); - $contributionPage = array( - 'contribution_page' => array( + $contributionPage = [ + 'contribution_page' => [ 'title' => ts('Contribution Page'), 'name' => 'contribution_page', 'where' => 'civicrm_contribution_page.title', 'data_type' => CRM_Utils_Type::T_STRING, - ), - ); + ], + ]; - $contributionNote = array( - 'contribution_note' => array( + $contributionNote = [ + 'contribution_note' => [ 'title' => ts('Contribution Note'), 'name' => 'contribution_note', 'data_type' => CRM_Utils_Type::T_TEXT, - ), - ); + ], + ]; - $extraFields = array( - 'contribution_batch' => array( + $extraFields = [ + 'contribution_batch' => [ 'title' => ts('Batch Name'), - ), - ); + ], + ]; // CRM-17787 - $campaignTitle = array( - 'contribution_campaign_title' => array( + $campaignTitle = [ + 'contribution_campaign_title' => [ 'title' => ts('Campaign Title'), 'name' => 'campaign_title', 'where' => 'civicrm_campaign.title', 'data_type' => CRM_Utils_Type::T_STRING, - ), - ); - $softCreditFields = array( - 'contribution_soft_credit_name' => array( + ], + ]; + $softCreditFields = [ + 'contribution_soft_credit_name' => [ 'name' => 'contribution_soft_credit_name', 'title' => ts('Soft Credit For'), 'where' => 'civicrm_contact_d.display_name', 'data_type' => CRM_Utils_Type::T_STRING, - ), - 'contribution_soft_credit_amount' => array( + ], + 'contribution_soft_credit_amount' => [ 'name' => 'contribution_soft_credit_amount', 'title' => ts('Soft Credit Amount'), 'where' => 'civicrm_contribution_soft.amount', 'data_type' => CRM_Utils_Type::T_MONEY, - ), - 'contribution_soft_credit_type' => array( + ], + 'contribution_soft_credit_type' => [ 'name' => 'contribution_soft_credit_type', 'title' => ts('Soft Credit Type'), 'where' => 'contribution_softcredit_type.label', 'data_type' => CRM_Utils_Type::T_STRING, - ), - 'contribution_soft_credit_contribution_id' => array( + ], + 'contribution_soft_credit_contribution_id' => [ 'name' => 'contribution_soft_credit_contribution_id', 'title' => ts('Soft Credit For Contribution ID'), 'where' => 'civicrm_contribution_soft.contribution_id', 'data_type' => CRM_Utils_Type::T_INT, - ), - 'contribution_soft_credit_contact_id' => array( + ], + 'contribution_soft_credit_contact_id' => [ 'name' => 'contribution_soft_credit_contact_id', 'title' => ts('Soft Credit For Contact ID'), 'where' => 'civicrm_contact_d.id', 'data_type' => CRM_Utils_Type::T_INT, - ), - ); - - // CRM-16713 - contribution search by Premiums on 'Find Contribution' form. - $premiums = array( - 'contribution_product_id' => array( - 'title' => ts('Premium'), - 'name' => 'contribution_product_id', - 'where' => 'civicrm_product.id', - 'data_type' => CRM_Utils_Type::T_INT, - ), - ); + ], + ]; - $fields = array_merge($impFields, $typeField, $contributionPage, $expFieldProduct, - $expFieldsContrib, $contributionNote, $extraFields, $softCreditFields, $financialAccount, $premiums, $campaignTitle, + $fields = array_merge($fields, $contributionPage, + $contributionNote, $extraFields, $softCreditFields, $financialAccount, $campaignTitle, CRM_Core_BAO_CustomField::getFieldsForImport('Contribution', FALSE, FALSE, FALSE, $checkPermission) ); @@ -895,6 +906,354 @@ public static function &exportableFields($checkPermission = TRUE) { return self::$_exportableFields; } + /** + * Record an activity when a payment is received. + * + * @todo this is intended to be moved to payment BAO class as a protected function + * on that class. Currently being cleaned up. The addActivityForPayment doesn't really + * merit it's own function as it makes the code less rather than more readable. + * + * @param int $contributionId + * @param int $participantId + * @param string $totalAmount + * @param string $currency + * @param string $trxnDate + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + public static function recordPaymentActivity($contributionId, $participantId, $totalAmount, $currency, $trxnDate) { + $activityType = ($totalAmount < 0) ? 'Refund' : 'Payment'; + + if ($participantId) { + $inputParams['id'] = $participantId; + $values = []; + $ids = []; + $entityObj = CRM_Event_BAO_Participant::getValues($inputParams, $values, $ids); + $entityObj = $entityObj[$participantId]; + $title = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Event', $entityObj->event_id, 'title'); + } + else { + $entityObj = new CRM_Contribute_BAO_Contribution(); + $entityObj->id = $contributionId; + $entityObj->find(TRUE); + $title = ts('Contribution'); + } + // @todo per block above this is not a logical splitting off of functionality. + self::addActivityForPayment($entityObj->contact_id, $activityType, $title, $contributionId, $totalAmount, $currency, $trxnDate); + } + + /** + * Get the value for the To Financial Account. + * + * @param $contribution + * @param $params + * + * @return int + */ + public static function getToFinancialAccount($contribution, $params) { + if (!empty($params['payment_processor'])) { + return CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contribution['payment_processor'], NULL, 'civicrm_payment_processor'); + } + if (!empty($params['payment_instrument_id'])) { + return CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contribution['payment_instrument_id']); + } + else { + $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); + $queryParams = [1 => [$relationTypeId, 'Integer']]; + return CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams); + } + } + + /** + * Get memberships realted to the contribution. + * + * @param int $contributionID + * + * @return array + */ + protected static function getRelatedMemberships($contributionID) { + $membershipPayments = civicrm_api3('MembershipPayment', 'get', [ + 'return' => 'membership_id', + 'contribution_id' => (int) $contributionID, + ])['values']; + $membershipIDs = []; + foreach ($membershipPayments as $membershipPayment) { + $membershipIDs[] = $membershipPayment['membership_id']; + } + if (empty($membershipIDs)) { + return []; + } + // We could combine this with the MembershipPayment.get - we'd + // need to re-wrangle the params (here or in the calling function) + // as they would then me membership.contact_id, membership.is_test etc + return civicrm_api3('Membership', 'get', [ + 'id' => ['IN' => $membershipIDs], + 'return' => ['id', 'contact_id', 'membership_type_id', 'is_test', 'status_id', 'end_date'], + ])['values']; + } + + /** + * Cancel contribution. + * + * This function should only be called from transitioncomponents - it is an interim step in refactoring. + * + * @param $processContributionObject + * @param $memberships + * @param $contributionId + * @param $membershipStatuses + * @param $updateResult + * @param $participant + * @param $oldStatus + * @param $pledgePayment + * @param $pledgeID + * @param $pledgePaymentIDs + * @param $contributionStatusId + * + * @return array + */ + protected static function cancel($processContributionObject, $memberships, $contributionId, $membershipStatuses, $updateResult, $participant, $oldStatus, $pledgePayment, $pledgeID, $pledgePaymentIDs, $contributionStatusId) { + // @fixme https://lab.civicrm.org/dev/core/issues/927 Cancelling membership etc is not desirable for all use-cases and we should be able to disable it + $processContribution = FALSE; + $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); + if (is_array($memberships)) { + foreach ($memberships as $membership) { + $update = TRUE; + //Update Membership status if there is no other completed contribution associated with the membership. + $relatedContributions = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id, TRUE); + foreach ($relatedContributions as $contriId) { + if ($contriId == $contributionId) { + continue; + } + $statusId = CRM_Core_DAO::getFieldValue('CRM_Contribute_BAO_Contribution', $contriId, 'contribution_status_id'); + if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $statusId) === 'Completed') { + $update = FALSE; + } + } + if ($membership && $update) { + $newStatus = array_search('Cancelled', $membershipStatuses); + + // Create activity + $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'get'); + $activityParam = [ + 'subject' => "Status changed from {$allStatus[$membership->status_id]} to {$allStatus[$newStatus]}", + 'source_contact_id' => CRM_Core_Session::singleton()->get('userID'), + 'target_contact_id' => $membership->contact_id, + 'source_record_id' => $membership->id, + 'activity_type_id' => 'Change Membership Status', + 'status_id' => 'Completed', + 'priority_id' => 'Normal', + 'activity_date_time' => 'now', + ]; + + $membership->status_id = $newStatus; + $membership->is_override = TRUE; + $membership->status_override_end_date = 'null'; + $membership->save(); + civicrm_api3('activity', 'create', $activityParam); + + $updateResult['updatedComponents']['CiviMember'] = $membership->status_id; + if ($processContributionObject) { + $processContribution = TRUE; + } + } + } + } + + if ($participant) { + $updatedStatusId = array_search('Cancelled', $participantStatuses); + CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE); + + $updateResult['updatedComponents']['CiviEvent'] = $updatedStatusId; + if ($processContributionObject) { + $processContribution = TRUE; + } + } + + if ($pledgePayment) { + CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId); + + $updateResult['updatedComponents']['CiviPledge'] = $contributionStatusId; + if ($processContributionObject) { + $processContribution = TRUE; + } + } + return [$updateResult, $processContribution]; + } + + /** + * Do any accounting updates required as a result of a contribution status change. + * + * Currently we have a bit of a roundabout where adding a payment results in this being called & + * this may attempt to add a payment. We need to resolve that.... + * + * The 'right' way to add payments or refunds is through the Payment.create api. That api + * then updates the contribution but this process should not also record another financial trxn. + * Currently we have weak detection fot that scenario & where it is detected the first returned + * value is FALSE - meaning 'do not continue'. + * + * We should also look at the fact that the calling function - updateFinancialAccounts + * bunches together some disparate processes rather than having separate appropriate + * functions. + * + * @param array $params + * + * @return bool[] + * Return indicates whether the updateFinancialAccounts function should continue & whether this is a refund. + */ + private static function updateFinancialAccountsOnContributionStatusChange(&$params) { + $previousContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($params['prevContribution']->contribution_status_id, 'name'); + $currentContributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id); + + $isARefund = FALSE; + if ((($previousContributionStatus == 'Partially paid' && $currentContributionStatus == 'Completed') + || ($previousContributionStatus == 'Pending refund' && $currentContributionStatus == 'Completed') + // This concept of pay_later as different to any other sort of pending is deprecated & it's unclear + // why it is here or where it is handled instead. + || ($previousContributionStatus == 'Pending' && $params['prevContribution']->is_pay_later == TRUE + && $currentContributionStatus == 'Partially paid')) + ) { + return [FALSE, $isARefund]; + } + + if ($previousContributionStatus == 'Completed' + && (self::isContributionStatusNegative($params['contribution']->contribution_status_id)) + ) { + $isARefund = TRUE; + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['trxnParams']['total_amount'] = -$params['total_amount']; + if (empty($params['contribution']->creditnote_id)) { + // This is always set in the Contribution::create function. + CRM_Core_Error::deprecatedFunctionWarning('Logic says this line is never reached & can be removed'); + $creditNoteId = self::createCreditNoteId(); + CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $params['contribution']->id, 'creditnote_id', $creditNoteId); + } + } + elseif (($previousContributionStatus == 'Pending' + && $params['prevContribution']->is_pay_later) || $previousContributionStatus == 'In Progress' + ) { + $financialTypeID = CRM_Utils_Array::value('financial_type_id', $params) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id; + $arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeID, 'Accounts Receivable Account is'); + + if ($currentContributionStatus == 'Cancelled') { + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['trxnParams']['to_financial_account_id'] = $arAccountId; + $params['trxnParams']['total_amount'] = -$params['total_amount']; + if (empty($params['contribution']->creditnote_id)) { + // This is always set in the Contribution::create function. + CRM_Core_Error::deprecatedFunctionWarning('Logic says this line is never reached & can be removed'); + $creditNoteId = self::createCreditNoteId(); + CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $params['contribution']->id, 'creditnote_id', $creditNoteId); + } + } + else { + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['trxnParams']['from_financial_account_id'] = $arAccountId; + } + } + + if (($previousContributionStatus == 'Pending' + || $previousContributionStatus == 'In Progress') + && ($currentContributionStatus == 'Completed') + ) { + if (empty($params['line_item'])) { + //CRM-15296 + //@todo - check with Joe regarding this situation - payment processors create pending transactions with no line items + // when creating recurring membership payment - there are 2 lines to comment out in contributonPageTest if fixed + // & this can be removed + return [FALSE, $isARefund]; + } + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + // This is an update so original currency if none passed in. + $params['trxnParams']['currency'] = CRM_Utils_Array::value('currency', $params, $params['prevContribution']->currency); + + self::recordAlwaysAccountsReceivable($params['trxnParams'], $params); + $trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']); + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['entity_id'] = self::$_trxnIDs[] = $trxn->id; + $query = "UPDATE civicrm_financial_item SET status_id = %1 WHERE entity_id = %2 and entity_table = 'civicrm_line_item'"; + $sql = "SELECT id, amount FROM civicrm_financial_item WHERE entity_id = %1 and entity_table = 'civicrm_line_item'"; + + $entityParams = [ + 'entity_table' => 'civicrm_financial_item', + ]; + foreach ($params['line_item'] as $fieldId => $fields) { + foreach ($fields as $fieldValueId => $lineItemDetails) { + $fparams = [ + 1 => [ + CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'), + 'Integer', + ], + 2 => [$lineItemDetails['id'], 'Integer'], + ]; + CRM_Core_DAO::executeQuery($query, $fparams); + $fparams = [ + 1 => [$lineItemDetails['id'], 'Integer'], + ]; + $financialItem = CRM_Core_DAO::executeQuery($sql, $fparams); + while ($financialItem->fetch()) { + $entityParams['entity_id'] = $financialItem->id; + $entityParams['amount'] = $financialItem->amount; + foreach (self::$_trxnIDs as $tID) { + $entityParams['financial_trxn_id'] = $tID; + CRM_Financial_BAO_FinancialItem::createEntityTrxn($entityParams); + } + } + } + } + return [FALSE, $isARefund]; + } + return [TRUE, $isARefund]; + } + + /** + * It is possible to override the membership id that is updated from the payment processor. + * + * Historically Paypal does this & it still does if it determines data is messed up - see + * https://lab.civicrm.org/dev/membership/issues/13 + * + * Read the comment block on repeattransaction for more information + * about how things should work. + * + * @param int $contributionID + * @param array $input + * + * @throws \CiviCRM_API3_Exception + */ + protected static function handleMembershipIDOverride($contributionID, $input) { + if (!empty($input['membership_id'])) { + Civi::log()->debug('The related membership id has been overridden - this may impact data - see https://github.com/civicrm/civicrm-core/pull/15053'); + civicrm_api3('MembershipPayment', 'create', ['contribution_id' => $contributionID, 'membership_id' => $input['membership_id']]); + } + } + + /** + * @inheritDoc + */ + public function addSelectWhereClause() { + $whereClauses = parent::addSelectWhereClause(); + if ($whereClauses !== []) { + // In this case permisssions have been applied & we assume the + // financialaclreport is applying these + // https://github.com/JMAConsulting/biz.jmaconsulting.financialaclreport/blob/master/financialaclreport.php#L107 + return $whereClauses; + } + + if (!CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()) { + return $whereClauses; + } + $types = CRM_Financial_BAO_FinancialType::getAllEnabledAvailableFinancialTypes(); + if (empty($types)) { + $whereClauses['financial_type_id'] = 'IN (0)'; + } + else { + $whereClauses['financial_type_id'] = [ + 'IN (' . implode(',', array_keys($types)) . ')', + ]; + } + return $whereClauses; + } + /** * @param null $status * @param null $startDate @@ -903,7 +1262,7 @@ public static function &exportableFields($checkPermission = TRUE) { * @return array|null */ public static function getTotalAmountAndCount($status = NULL, $startDate = NULL, $endDate = NULL) { - $where = array(); + $where = []; switch ($status) { case 'Valid': $where[] = 'contribution_status_id = 1'; @@ -920,13 +1279,18 @@ public static function getTotalAmountAndCount($status = NULL, $startDate = NULL, if ($endDate) { $where[] = "receive_date <= '" . CRM_Utils_Type::escape($endDate, 'Timestamp') . "'"; } - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); - if ($financialTypes) { - $where[] = "c.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; - $where[] = "i.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; - } - else { - $where[] = "c.financial_type_id IN (0)"; + $financialTypeACLJoin = ''; + if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()) { + $financialTypeACLJoin = " LEFT JOIN civicrm_line_item i ON (i.contribution_id = c.id AND i.entity_table = 'civicrm_contribution') "; + $financialTypes = CRM_Contribute_PseudoConstant::financialType(); + CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); + if ($financialTypes) { + $where[] = "c.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; + $where[] = "i.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; + } + else { + $where[] = "c.financial_type_id IN (0)"; + } } $whereCond = implode(' AND ', $where); @@ -937,7 +1301,7 @@ public static function getTotalAmountAndCount($status = NULL, $startDate = NULL, currency FROM civicrm_contribution c INNER JOIN civicrm_contact contact ON ( contact.id = c.contact_id ) -LEFT JOIN civicrm_line_item i ON ( i.contribution_id = c.id AND i.entity_table = 'civicrm_contribution' ) + $financialTypeACLJoin WHERE $whereCond AND ( is_test = 0 OR is_test IS NULL ) AND contact.is_deleted = 0 @@ -945,17 +1309,17 @@ public static function getTotalAmountAndCount($status = NULL, $startDate = NULL, "; $dao = CRM_Core_DAO::executeQuery($query); - $amount = array(); + $amount = []; $count = 0; while ($dao->fetch()) { $count += $dao->total_count; $amount[] = CRM_Utils_Money::format($dao->total_amount, $dao->currency); } if ($count) { - return array( + return [ 'amount' => implode(', ', $amount), 'count' => $count, - ); + ]; } return NULL; } @@ -975,11 +1339,11 @@ public static function deleteContribution($id) { $results = NULL; //delete activity record - $params = array( + $params = [ 'source_record_id' => $id, // activity type id for contribution 'activity_type_id' => 6, - ); + ]; CRM_Activity_BAO_Activity::deleteActivity($params); @@ -1023,10 +1387,10 @@ public static function deleteContribution($id) { CRM_Utils_Hook::post('delete', 'Contribution', $dao->id, $dao); // delete the recently created Contribution - $contributionRecent = array( + $contributionRecent = [ 'id' => $id, 'type' => 'Contribution', - ); + ]; CRM_Utils_Recent::del($contributionRecent); return $results; @@ -1045,20 +1409,19 @@ public static function deleteContribution($id) { * @throws \CiviCRM_API3_Exception */ public static function failPayment($contributionID, $contactID, $message) { - civicrm_api3('activity', 'create', array( + civicrm_api3('activity', 'create', [ 'activity_type_id' => 'Failed Payment', 'details' => $message, 'subject' => ts('Payment failed at payment processor'), 'source_record_id' => $contributionID, - 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ? CRM_Core_Session::getLoggedInContactID() : - $contactID, - )); + 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ? CRM_Core_Session::getLoggedInContactID() : $contactID, + ]); // CRM-20336 Make sure that the contribution status is Failed, not Pending. - civicrm_api3('contribution', 'create', array( + civicrm_api3('contribution', 'create', [ 'id' => $contributionID, 'contribution_status_id' => 'Failed', - )); + ]); } /** @@ -1080,17 +1443,17 @@ public static function checkDuplicate($input, &$duplicates, $id = NULL) { $trxn_id = CRM_Utils_Array::value('trxn_id', $input); $invoice_id = CRM_Utils_Array::value('invoice_id', $input); - $clause = array(); - $input = array(); + $clause = []; + $input = []; if ($trxn_id) { $clause[] = "trxn_id = %1"; - $input[1] = array($trxn_id, 'String'); + $input[1] = [$trxn_id, 'String']; } if ($invoice_id) { $clause[] = "invoice_id = %2"; - $input[2] = array($invoice_id, 'String'); + $input[2] = [$invoice_id, 'String']; } if (empty($clause)) { @@ -1100,7 +1463,7 @@ public static function checkDuplicate($input, &$duplicates, $id = NULL) { $clause = implode(' OR ', $clause); if ($id) { $clause = "( $clause ) AND id != %3"; - $input[3] = array($id, 'Integer'); + $input[3] = [$id, 'Integer']; } $query = "SELECT id FROM civicrm_contribution WHERE $clause"; @@ -1172,38 +1535,38 @@ public static function getContributionFields($addExtraFields = TRUE) { * Add extra fields specific to contribution. */ public static function getSpecialContributionFields() { - $extraFields = array( - 'contribution_soft_credit_name' => array( + $extraFields = [ + 'contribution_soft_credit_name' => [ 'name' => 'contribution_soft_credit_name', 'title' => ts('Soft Credit Name'), 'headerPattern' => '/^soft_credit_name$/i', 'where' => 'civicrm_contact_d.display_name', - ), - 'contribution_soft_credit_email' => array( + ], + 'contribution_soft_credit_email' => [ 'name' => 'contribution_soft_credit_email', 'title' => ts('Soft Credit Email'), 'headerPattern' => '/^soft_credit_email$/i', 'where' => 'soft_email.email', - ), - 'contribution_soft_credit_phone' => array( + ], + 'contribution_soft_credit_phone' => [ 'name' => 'contribution_soft_credit_phone', 'title' => ts('Soft Credit Phone'), 'headerPattern' => '/^soft_credit_phone$/i', 'where' => 'soft_phone.phone', - ), - 'contribution_soft_credit_contact_id' => array( + ], + 'contribution_soft_credit_contact_id' => [ 'name' => 'contribution_soft_credit_contact_id', 'title' => ts('Soft Credit Contact ID'), 'headerPattern' => '/^soft_credit_contact_id$/i', 'where' => 'civicrm_contribution_soft.contact_id', - ), - 'contribution_pcp_title' => array( + ], + 'contribution_pcp_title' => [ 'name' => 'contribution_pcp_title', 'title' => ts('Personal Campaign Page Title'), 'headerPattern' => '/^contribution_pcp_title$/i', 'where' => 'contribution_pcp.title', - ), - ); + ], + ]; return $extraFields; } @@ -1225,14 +1588,14 @@ public static function getCurrentandGoalAmount($pageID) { "; $config = CRM_Core_Config::singleton(); - $params = array(1 => array($pageID, 'Integer')); + $params = [1 => [$pageID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); if ($dao->fetch()) { - return array($dao->goal, $dao->total); + return [$dao->goal, $dao->total]; } else { - return array(NULL, NULL); + return [NULL, NULL]; } } @@ -1241,17 +1604,17 @@ public static function getCurrentandGoalAmount($pageID) { * * The returned array provides details about the original contribution & donor. * - * @todo - this is a confusing function called from one place. It has a test. It would be - * nice to deprecate it. - * * @param int $honorId * In Honor of Contact ID. * * @return array * list of contribution fields + * @todo - this is a confusing function called from one place. It has a test. It would be + * nice to deprecate it. + * */ public static function getHonorContacts($honorId) { - $params = array(); + $params = []; $honorDAO = new CRM_Contribute_DAO_ContributionSoft(); $honorDAO->contact_id = $honorId; $honorDAO->find(); @@ -1271,7 +1634,7 @@ public static function getHonorContacts($honorId) { $params[$contributionDAO->id]['amount'] = CRM_Utils_Money::format($contributionDAO->total_amount, $contributionDAO->currency); $params[$contributionDAO->id]['source'] = $contributionDAO->source; $params[$contributionDAO->id]['receive_date'] = $contributionDAO->receive_date; - $params[$contributionDAO->id]['contribution_status'] = CRM_Contribute_PseudoConstant::contributionStatus($contributionDAO->contribution_status_id); + $params[$contributionDAO->id]['contribution_status'] = CRM_Contribute_PseudoConstant::contributionStatus($contributionDAO->contribution_status_id, 'label'); } } @@ -1300,76 +1663,23 @@ public static function sortName($id) { } /** - * @param int $contactID + * Generate summary of amount received in the current fiscal year to date from the contact or contacts. + * + * @param int|array $contactIDs * * @return array */ - public static function annual($contactID) { - if (is_array($contactID)) { - $contactIDs = implode(',', $contactID); - } - else { - $contactIDs = $contactID; - } - - $config = CRM_Core_Config::singleton(); - $startDate = $endDate = NULL; - - $currentMonth = date('m'); - $currentDay = date('d'); - if ((int ) $config->fiscalYearStart['M'] > $currentMonth || - ((int ) $config->fiscalYearStart['M'] == $currentMonth && - (int ) $config->fiscalYearStart['d'] > $currentDay - ) - ) { - $year = date('Y') - 1; + public static function annual($contactIDs) { + if (!is_array($contactIDs)) { + // In practice I can't fine any evidence that this function is ever called with + // anything other than a single contact id, but left like this due to .... fear. + $contactIDs = explode(',', $contactIDs); } - else { - $year = date('Y'); - } - $nextYear = $year + 1; - if ($config->fiscalYearStart) { - $newFiscalYearStart = $config->fiscalYearStart; - if ($newFiscalYearStart['M'] < 10) { - $newFiscalYearStart['M'] = '0' . $newFiscalYearStart['M']; - } - if ($newFiscalYearStart['d'] < 10) { - $newFiscalYearStart['d'] = '0' . $newFiscalYearStart['d']; - } - $config->fiscalYearStart = $newFiscalYearStart; - $monthDay = $config->fiscalYearStart['M'] . $config->fiscalYearStart['d']; - } - else { - $monthDay = '0101'; - } - $startDate = "$year$monthDay"; - $endDate = "$nextYear$monthDay"; - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); - $additionalWhere = " AND b.financial_type_id IN (0)"; - $liWhere = " AND i.financial_type_id IN (0)"; - if (!empty($financialTypes)) { - $additionalWhere = " AND b.financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ") AND i.id IS NULL"; - $liWhere = " AND i.financial_type_id NOT IN (" . implode(',', array_keys($financialTypes)) . ")"; - } - $query = " - SELECT count(*) as count, - sum(total_amount) as amount, - avg(total_amount) as average, - currency - FROM civicrm_contribution b - LEFT JOIN civicrm_line_item i ON i.contribution_id = b.id AND i.entity_table = 'civicrm_contribution' $liWhere - WHERE b.contact_id IN ( $contactIDs ) - AND b.contribution_status_id = 1 - AND b.is_test = 0 - AND b.receive_date >= $startDate - AND b.receive_date < $endDate - $additionalWhere - GROUP BY currency - "; + $query = self::getAnnualQuery($contactIDs); $dao = CRM_Core_DAO::executeQuery($query); $count = 0; - $amount = $average = array(); + $amount = $average = []; while ($dao->fetch()) { if ($dao->count > 0 && $dao->amount > 0) { $count += $dao->count; @@ -1378,13 +1688,13 @@ public static function annual($contactID) { } } if ($count > 0) { - return array( + return [ $count, implode(', ', $amount), implode(', ', $average), - ); + ]; } - return array(0, 0, 0); + return [0, 0, 0]; } /** @@ -1401,8 +1711,8 @@ public static function annual($contactID) { public static function checkDuplicateIds($params) { $dao = new CRM_Contribute_DAO_Contribution(); - $clause = array(); - $input = array(); + $clause = []; + $input = []; foreach ($params as $k => $v) { if ($v) { $clause[] = "$k = '$v'"; @@ -1424,14 +1734,14 @@ public static function checkDuplicateIds($params) { * * @param int $exportMode * Export mode. - * @param string $componentIds + * @param array $componentIds * Component ids. * * @return array * associated array */ public static function getContributionDetails($exportMode, $componentIds) { - $paymentDetails = array(); + $paymentDetails = []; $componentClause = ' IN ( ' . implode(',', $componentIds) . ' ) '; if ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT) { @@ -1471,13 +1781,13 @@ public static function getContributionDetails($exportMode, $componentIds) { $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $paymentDetails[$dao->id] = array( + $paymentDetails[$dao->id] = [ 'total_amount' => $dao->total_amount, 'contribution_status' => $dao->status, 'receive_date' => $dao->receive_date, 'pay_instru' => $dao->payment_instrument, 'trxn_id' => $dao->trxn_id, - ); + ]; } return $paymentDetails; @@ -1514,7 +1824,7 @@ public static function createAddress($params, $billingLocationTypeID) { * @param int $contactId */ public static function deleteAddress($contributionId = NULL, $contactId = NULL) { - $clauses = array(); + $clauses = []; $contactJoin = NULL; if ($contributionId) { @@ -1542,7 +1852,7 @@ public static function deleteAddress($contributionId = NULL, $contactId = NULL) $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $params = array('id' => $dao->id); + $params = ['id' => $dao->id]; CRM_Core_BAO_Block::blockDelete('Address', $params); } } @@ -1562,7 +1872,7 @@ public static function deleteAddress($contributionId = NULL, $contactId = NULL) public static function checkOnlinePendingContribution($componentId, $componentName) { $contributionId = NULL; if (!$componentId || - !in_array($componentName, array('Event', 'Membership')) + !in_array($componentName, ['Event', 'Membership']) ) { return $contributionId; } @@ -1603,7 +1913,6 @@ public static function checkOnlinePendingContribution($componentId, $componentNa strpos($dao->source, $source) !== FALSE ) { $contributionId = $dao->contribution_id; - $dao->free(); } } @@ -1615,16 +1924,16 @@ public static function checkOnlinePendingContribution($componentId, $componentNa * * This function by-passes hooks - to address this - don't use this function. * - * @deprecated - * - * Use api contribute.completetransaction - * For failures use failPayment (preferably exposing by api in the process). - * * @param array $params * @param bool $processContributionObject * * @return array * @throws \Exception + * @deprecated + * + * Use api contribute.completetransaction + * For failures use failPayment (preferably exposing by api in the process). + * */ public static function transitionComponents($params, $processContributionObject = FALSE) { // get minimum required values. @@ -1637,17 +1946,17 @@ public static function transitionComponents($params, $processContributionObject // if we already processed contribution object pass previous status id. $previousContriStatusId = CRM_Utils_Array::value('previous_contribution_status_id', $params); - $updateResult = array(); + $updateResult = []; $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); // we process only ( Completed, Cancelled, or Failed ) contributions. if (!$contributionId || - !in_array($contributionStatusId, array( + !in_array($contributionStatusId, [ array_search('Completed', $contributionStatuses), array_search('Cancelled', $contributionStatuses), array_search('Failed', $contributionStatuses), - )) + ]) ) { return $updateResult; } @@ -1684,7 +1993,7 @@ public static function transitionComponents($params, $processContributionObject $baseIPN = new CRM_Core_Payment_BaseIPN(); - $input = $ids = $objects = array(); + $input = $ids = $objects = []; $input['component'] = CRM_Utils_Array::value('component', $componentDetails); $ids['contribution'] = $contributionId; @@ -1704,9 +2013,9 @@ public static function transitionComponents($params, $processContributionObject $participant = &$objects['participant']; $pledgePayment = &$objects['pledge_payment']; $contribution = &$objects['contribution']; - + $pledgeID = $oldStatus = NULL; + $pledgePaymentIDs = []; if ($pledgePayment) { - $pledgePaymentIDs = array(); foreach ($pledgePayment as $key => $object) { $pledgePaymentIDs[] = $object->id; } @@ -1725,65 +2034,8 @@ public static function transitionComponents($params, $processContributionObject // we might want to process contribution object. $processContribution = FALSE; if ($contributionStatusId == array_search('Cancelled', $contributionStatuses)) { - if (is_array($memberships)) { - foreach ($memberships as $membership) { - $update = TRUE; - //Update Membership status if there is no other completed contribution associated with the membership. - $relatedContributions = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id, TRUE); - foreach ($relatedContributions as $contriId) { - if ($contriId == $contributionId) { - continue; - } - if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contriId) === 'Completed') { - $update = FALSE; - } - } - if ($membership && $update) { - $newStatus = array_search('Cancelled', $membershipStatuses); - - // Create activity - $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'get'); - $activityParam = array( - 'subject' => "Status changed from {$allStatus[$membership->status_id]} to {$allStatus[$newStatus]}", - 'source_contact_id' => CRM_Core_Session::singleton()->get('userID'), - 'target_contact_id' => $membership->contact_id, - 'source_record_id' => $membership->id, - 'activity_type_id' => 'Change Membership Status', - 'status_id' => 'Completed', - 'priority_id' => 'Normal', - 'activity_date_time' => 'now', - ); - - $membership->status_id = $newStatus; - $membership->save(); - civicrm_api3('activity', 'create', $activityParam); - - $updateResult['updatedComponents']['CiviMember'] = $membership->status_id; - if ($processContributionObject) { - $processContribution = TRUE; - } - } - } - } - - if ($participant) { - $updatedStatusId = array_search('Cancelled', $participantStatuses); - CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE); - - $updateResult['updatedComponents']['CiviEvent'] = $updatedStatusId; - if ($processContributionObject) { - $processContribution = TRUE; - } - } - - if ($pledgePayment) { - CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId); - - $updateResult['updatedComponents']['CiviPledge'] = $contributionStatusId; - if ($processContributionObject) { - $processContribution = TRUE; - } - } + // Call interim cancel function - with a goal to cleaning up the signature on it and switching to a tested api Contribution.cancel function. + list($updateResult, $processContribution) = self::cancel($processContributionObject, $memberships, $contributionId, $membershipStatuses, $updateResult, $participant, $oldStatus, $pledgePayment, $pledgeID, $pledgePaymentIDs, $contributionStatusId); } elseif ($contributionStatusId == array_search('Failed', $contributionStatuses)) { if (is_array($memberships)) { @@ -1795,12 +2047,15 @@ public static function transitionComponents($params, $processContributionObject if ($contriId == $contributionId) { continue; } - if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contriId) === 'Completed') { + $statusId = CRM_Core_DAO::getFieldValue('CRM_Contribute_BAO_Contribution', $contriId, 'contribution_status_id'); + if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $statusId) === 'Completed') { $update = FALSE; } } if ($membership && $update) { $membership->status_id = array_search('Expired', $membershipStatuses); + $membership->is_override = TRUE; + $membership->status_override_end_date = 'null'; $membership->save(); $updateResult['updatedComponents']['CiviMember'] = $membership->status_id; @@ -1833,13 +2088,19 @@ public static function transitionComponents($params, $processContributionObject // only pending contribution related object processed. if ($previousContriStatusId && - ($previousContriStatusId != array_search('Pending', $contributionStatuses)) + !in_array($contributionStatuses[$previousContriStatusId], [ + 'Pending', + 'Partially paid', + ]) ) { // this is case when we already processed contribution object. return $updateResult; } elseif (!$previousContriStatusId && - $contribution->contribution_status_id != array_search('Pending', $contributionStatuses) + !in_array($contributionStatuses[$contribution->contribution_status_id], [ + 'Pending', + 'Partially paid', + ]) ) { // this is case when we are going to process contribution object later. return $updateResult; @@ -1873,8 +2134,6 @@ public static function transitionComponents($params, $processContributionObject } } // else fall back to using current membership type - $dao->free(); - // Figure out number of terms $numterms = 1; $lineitems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contributionId); @@ -1924,14 +2183,14 @@ public static function transitionComponents($params, $processContributionObject (array) $membership ); - $formattedParams = array( + $formattedParams = [ 'status_id' => CRM_Utils_Array::value('id', $calcStatus, array_search('Current', $membershipStatuses) ), 'join_date' => CRM_Utils_Date::customFormat($dates['join_date'], $format), 'start_date' => CRM_Utils_Date::customFormat($dates['start_date'], $format), 'end_date' => CRM_Utils_Date::customFormat($dates['end_date'], $format), - ); + ]; CRM_Utils_Hook::pre('edit', 'Membership', $membership->id, $formattedParams); @@ -1939,7 +2198,7 @@ public static function transitionComponents($params, $processContributionObject $membership->save(); //updating the membership log - $membershipLog = array(); + $membershipLog = []; $membershipLog = $formattedParams; $logStartDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('log_start_date', $dates), $format); $logStartDate = ($logStartDate) ? CRM_Utils_Date::isoToMysql($logStartDate) : $formattedParams['start_date']; @@ -1950,29 +2209,29 @@ public static function transitionComponents($params, $processContributionObject $membershipLog['modified_date'] = date('Ymd'); $membershipLog['membership_type_id'] = $membership->membership_type_id; - CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray); + CRM_Member_BAO_MembershipLog::add($membershipLog); //update related Memberships. CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $formattedParams); - foreach (array('Membership Signup', 'Membership Renewal') as $activityType) { + foreach (['Membership Signup', 'Membership Renewal'] as $activityType) { $scheduledActivityID = CRM_Utils_Array::value('id', civicrm_api3('Activity', 'Get', - array( + [ 'source_record_id' => $membership->id, 'activity_type_id' => $activityType, 'status_id' => 'Scheduled', - 'options' => array( + 'options' => [ 'limit' => 1, 'sort' => 'id DESC', - ), - ) + ], + ] ) ); // 1. Update Schedule Membership Signup/Renewal activity to completed on successful payment of pending membership // 2. OR Create renewal activity scheduled if its membership renewal will be paid later if ($scheduledActivityID) { - CRM_Activity_BAO_Activity::addActivity($membership, $activityType, $membership->contact_id, array('id' => $scheduledActivityID)); + CRM_Activity_BAO_Activity::addActivity($membership, $activityType, $membership->contact_id, ['id' => $scheduledActivityID]); break; } } @@ -1983,17 +2242,14 @@ public static function transitionComponents($params, $processContributionObject CRM_Activity_BAO_Activity::addActivity($membership, 'Change Membership Status', NULL, - array( + [ 'subject' => "Status changed from {$allStatus[$oldStatus]} to {$allStatus[$membership->status_id]}", 'source_contact_id' => $membershipLog['modified_id'], 'priority_id' => 'Normal', - ) + ] ); } - $updateResult['membership_end_date'] = CRM_Utils_Date::customFormat($dates['end_date'], - '%B %E%f, %Y' - ); $updateResult['updatedComponents']['CiviMember'] = $membership->status_id; if ($processContributionObject) { $processContribution = TRUE; @@ -2026,8 +2282,8 @@ public static function transitionComponents($params, $processContributionObject // process contribution object. if ($processContribution) { - $contributionParams = array(); - $fields = array( + $contributionParams = []; + $fields = [ 'contact_id', 'total_amount', 'receive_date', @@ -2041,7 +2297,7 @@ public static function transitionComponents($params, $processContributionObject 'non_deductible_amount', 'receipt_date', 'check_number', - ); + ]; foreach ($fields as $field) { if (empty($params[$field])) { continue; @@ -2049,7 +2305,7 @@ public static function transitionComponents($params, $processContributionObject $contributionParams[$field] = $params[$field]; } - $ids = array('contribution' => $contributionId); + $ids = ['contribution' => $contributionId]; $contribution = CRM_Contribute_BAO_Contribution::create($contributionParams, $ids); } @@ -2064,7 +2320,7 @@ public static function transitionComponents($params, $processContributionObject * @return array */ public static function getComponentDetails($contributionId) { - $componentDetails = $pledgePayment = array(); + $componentDetails = $pledgePayment = []; if (!$contributionId) { return $componentDetails; } @@ -2087,7 +2343,7 @@ public static function getComponentDetails($contributionId) { WHERE c.id = $contributionId"; $dao = CRM_Core_DAO::executeQuery($query); - $componentDetails = array(); + $componentDetails = []; while ($dao->fetch()) { $componentDetails['component'] = $dao->participant_id ? 'event' : 'contribute'; @@ -2100,7 +2356,7 @@ public static function getComponentDetails($contributionId) { } if ($dao->membership_id) { if (!isset($componentDetails['membership'])) { - $componentDetails['membership'] = $componentDetails['membership_type'] = array(); + $componentDetails['membership'] = $componentDetails['membership_type'] = []; } $componentDetails['membership'][] = $dao->membership_id; $componentDetails['membership_type'][] = $dao->membership_type_id; @@ -2130,7 +2386,7 @@ public static function contributionCount($contactId, $includeSoftCredit = TRUE) if (!$contactId) { return 0; } - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); + $financialTypes = CRM_Financial_BAO_FinancialType::getAllAvailableFinancialTypes(); $additionalWhere = " AND contribution.financial_type_id IN (0)"; $liWhere = " AND i.financial_type_id IN (0)"; if (!empty($financialTypes)) { @@ -2166,18 +2422,42 @@ public static function contributionCount($contactId, $includeSoftCredit = TRUE) /** * Repeat a transaction as part of a recurring series. * - * Only call this via the api as it is being refactored. The intention is that the repeatTransaction function - * (possibly living on the ContributionRecur BAO) would be called first to create a pending contribution with a - * subsequent call to the contribution.completetransaction api. - * - * The completeTransaction functionality has historically been overloaded to both complete and repeat payments. + * The ideal flow is + * 1) Processor calls contribution.repeattransaction with contribution_status_id = Pending + * 2) The repeattransaction loads the 'template contribution' and calls a hook to allow altering of it . + * 3) Repeat transaction calls order.create to create the pending contribution with correct line items + * and associated entities. + * 4) The calling code calls Payment.create which in turn calls CompleteOrder (if completing) + * which updates the various entities and sends appropriate emails. + * + * Gaps in the above (@todo) + * 1) many processors still call repeattransaction with contribution_status_id = Completed + * 2) repeattransaction code is current munged into completeTransaction code for historical bad coding reasons + * 3) Repeat transaction duplicates rather than calls Order.create + * 4) Use of payment.create still limited - completetransaction is more common. + * 5) the template transaction is tricky - historically we used the first contribution + * linked to a recurring contribution. More recently that was changed to be the most recent. + * Ideally it would be an actual template - not a contribution used as a template which + * would give more appropriate flexibility. Note line_items have an entity so that table + * could be used for the line item template - the difficulty is the custom fields... + * 6) the determination of the membership to be linked is tricksy. The prioritised method is + * to load the membership(s) referred to via line items in the template transactions. Any other + * method is likely to lead to incorrect line items & related entities being created (as the line_item + * link is a required part of 'correct data'). However there are 3 other methods to determine it + * - membership_payment record + * - civicrm_membership.contribution_recur_id + * - input override. + * Passing in an input override WILL ensure the membership is extended to prevent regressions + * of historical processors since this has been handled 'forever' - specifically for paypal. + * albeit by an even nastier mechanism than the current input override. + * The count is out on how correct related entities wind up in this case. * * @param CRM_Contribute_BAO_Contribution $contribution * @param array $input * @param array $contributionParams * @param int $paymentProcessorID * - * @return array + * @return bool * @throws CiviCRM_API3_Exception */ protected static function repeatTransaction(&$contribution, &$input, $contributionParams, $paymentProcessorID) { @@ -2191,9 +2471,9 @@ protected static function repeatTransaction(&$contribution, &$input, $contributi } if (!empty($contributionParams['contribution_recur_id'])) { - $recurringContribution = civicrm_api3('ContributionRecur', 'getsingle', array( + $recurringContribution = civicrm_api3('ContributionRecur', 'getsingle', [ 'id' => $contributionParams['contribution_recur_id'], - )); + ]); if (!empty($recurringContribution['campaign_id'])) { // CRM-17718 the campaign id on the contribution recur record should get precedence. $contributionParams['campaign_id'] = $recurringContribution['campaign_id']; @@ -2205,7 +2485,10 @@ protected static function repeatTransaction(&$contribution, &$input, $contributi } $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution( $contributionParams['contribution_recur_id'], - array_intersect_key($contributionParams, array('total_amount' => TRUE, 'financial_type_id' => TRUE)) + array_intersect_key($contributionParams, [ + 'total_amount' => TRUE, + 'financial_type_id' => TRUE, + ]) ); $input['line_item'] = $contributionParams['line_item'] = $templateContribution['line_item']; @@ -2227,10 +2510,14 @@ protected static function repeatTransaction(&$contribution, &$input, $contributi if (isset($contribution->contribution_page_id) && is_numeric($contribution->contribution_page_id)) { $contributionParams['contribution_page_id'] = $contribution->contribution_page_id; } + if (!empty($contribution->tax_amount)) { + $contributionParams['tax_amount'] = $contribution->tax_amount; + } $createContribution = civicrm_api3('Contribution', 'create', $contributionParams); $contribution->id = $createContribution['id']; CRM_Contribute_BAO_ContributionRecur::copyCustomValues($contributionParams['contribution_recur_id'], $contribution->id); + self::handleMembershipIDOverride($contribution->id, $input); return TRUE; } } @@ -2248,7 +2535,7 @@ protected static function repeatTransaction(&$contribution, &$input, $contributi */ public static function getOnbehalfIds($contributionId, $contributorId = NULL) { - $ids = array(); + $ids = []; if (!$contributionId) { return $ids; @@ -2274,14 +2561,14 @@ public static function getOnbehalfIds($contributionId, $contributorId = NULL) { AND civicrm_activity_contact.record_type_id = %3 "; - $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name'); + $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate'); $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts); - $params = array( - 1 => array($activityTypeId, 'Integer'), - 2 => array($contributionId, 'Integer'), - 3 => array($sourceID, 'Integer'), - ); + $params = [ + 1 => [$activityTypeId, 'Integer'], + 2 => [$contributionId, 'Integer'], + 3 => [$sourceID, 'Integer'], + ]; $sourceContactId = CRM_Core_DAO::singleValueQuery($activityQuery, $params); @@ -2327,7 +2614,7 @@ public static function getContributionDates() { else { $year = date('Y'); } - $year = array('Y' => $year); + $year = ['Y' => $year]; $yearDate = $config->fiscalYearStart; $yearDate = array_merge($year, $yearDate); $yearDate = CRM_Utils_Date::format($yearDate); @@ -2336,17 +2623,18 @@ public static function getContributionDates() { $now = date('Ymd'); - return array( + return [ 'now' => $now, 'yearDate' => $yearDate, 'monthDate' => $monthDate, - ); + ]; } /** * Load objects relations to contribution object. * Objects are stored in the $_relatedObjects property * In the first instance we are just moving functionality from BASEIpn - + * * @see http://issues.civicrm.org/jira/browse/CRM-9996 * * Note that the unit test for the BaseIPN class tests this function @@ -2362,6 +2650,14 @@ public static function getContributionDates() { * @throws Exception */ public function loadRelatedObjects(&$input, &$ids, $loadAll = FALSE) { + // @todo deprecate this function - the steps should be + // 1) add additional functions like 'getRelatedMemberships' + // 2) switch all calls that refer to ->_relatedObjects to + // using the helper functions + // 3) make ->_relatedObjects noisy in some way (deprecation won't work for properties - hmm + // 4) make ->_relatedObjects protected + // 5) hone up the individual functions to not use rely on this having been called + // 6) deprecate like mad if ($loadAll) { $ids = array_merge($this->getComponentDetails($this->id), $ids); if (empty($ids['contact']) && isset($this->contact_id)) { @@ -2424,7 +2720,9 @@ public function loadRelatedObjects(&$input, &$ids, $loadAll = FALSE) { } } - $this->loadRelatedMembershipObjects($ids); + // These are probably no longer accessed from anywhere + // @todo remove this line, after ensuring not used. + $ids = $this->loadRelatedMembershipObjects($ids); if ($this->_component != 'contribute') { // we are in event mode @@ -2467,6 +2765,9 @@ public function loadRelatedObjects(&$input, &$ids, $loadAll = FALSE) { $ids['paymentProcessor'] = $paymentProcessorID; $this->_relatedObjects['paymentProcessor'] = $paymentProcessor; } + + // Add contribution id to $ids. CRM-20401 + $ids['contribution'] = $this->id; return TRUE; } @@ -2537,20 +2838,23 @@ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText } // todo remove strtolower - check consistency if (strtolower($this->_component) == 'event') { - $eventParams = array('id' => $this->_relatedObjects['participant']->event_id); - $values['event'] = array(); + $eventParams = ['id' => $this->_relatedObjects['participant']->event_id]; + $values['event'] = []; CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); //get location details - $locationParams = array('entity_id' => $this->_relatedObjects['participant']->event_id, 'entity_table' => 'civicrm_event'); + $locationParams = [ + 'entity_id' => $this->_relatedObjects['participant']->event_id, + 'entity_table' => 'civicrm_event', + ]; $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); - $ufJoinParams = array( + $ufJoinParams = [ 'entity_table' => 'civicrm_event', 'entity_id' => $ids['event'], 'module' => 'CiviEvent', - ); + ]; list($custom_pre_id, $custom_post_ids @@ -2586,12 +2890,12 @@ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText if (isset($ids['onbehalf_dupe_alert'])) { $values['onbehalf_dupe_alert'] = $ids['onbehalf_dupe_alert']; } - $entityBlock = array( + $entityBlock = [ 'contact_id' => $ids['contact'], 'location_type_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_LocationType', 'Home', 'id', 'name' ), - ); + ]; $address = CRM_Core_BAO_Address::getValues($entityBlock); $template->assign('onBehalfAddress', $address[$entityBlock['location_type_id']]['display']); } @@ -2602,6 +2906,7 @@ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText if (!empty($this->_relatedObjects['membership'])) { foreach ($this->_relatedObjects['membership'] as $membership) { if ($membership->id) { + $values['membership_id'] = $membership->id; $values['isMembership'] = TRUE; $values['membership_assign'] = TRUE; @@ -2658,17 +2963,18 @@ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText * The set of ids related to the input. * * @return array + * @throws \CRM_Core_Exception */ - public function _gatherMessageValues($input, &$values, $ids = array()) { + public function _gatherMessageValues($input, &$values, $ids = []) { // set display address of contributor if ($this->address_id) { - $addressParams = array('id' => $this->address_id); + $addressParams = ['id' => $this->address_id]; $addressDetails = CRM_Core_BAO_Address::getValues($addressParams, FALSE, 'id'); $addressDetails = array_values($addressDetails); } // Else we assign the billing address of the contribution contact. else { - $addressParams = array('contact_id' => $this->contact_id, 'is_billing' => 1); + $addressParams = ['contact_id' => $this->contact_id, 'is_billing' => 1]; $addressDetails = (array) CRM_Core_BAO_Address::getValues($addressParams); $addressDetails = array_values($addressDetails); } @@ -2690,15 +2996,9 @@ public function _gatherMessageValues($input, &$values, $ids = array()) { // This is precautionary as there are some legacy flows, but it should really be // loaded by now. if (!isset($this->_relatedObjects['contributionPage'])) { - $this->loadRelatedEntitiesByID(array('contributionPage' => $this->contribution_page_id)); + $this->loadRelatedEntitiesByID(['contributionPage' => $this->contribution_page_id]); } - // CRM-8254 - override default currency if applicable - $config = CRM_Core_Config::singleton(); - $config->defaultCurrency = CRM_Utils_Array::value( - 'currency', - $values, - $config->defaultCurrency - ); + CRM_Contribute_BAO_Contribution_Utils::overrideDefaultCurrency($values); } } // no contribution page -probably back office @@ -2711,19 +3011,25 @@ public function _gatherMessageValues($input, &$values, $ids = array()) { $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->id); if (!empty($lineItems)) { $firstLineItem = reset($lineItems); - $priceSet = civicrm_api3('PriceSet', 'getsingle', array('id' => $firstLineItem['price_set_id'], 'return' => 'is_quick_config, id')); - $values['priceSetID'] = $priceSet['id']; + $priceSet = []; + if (!empty($firstLineItem['price_set_id'])) { + $priceSet = civicrm_api3('PriceSet', 'getsingle', [ + 'id' => $firstLineItem['price_set_id'], + 'return' => 'is_quick_config, id', + ]); + $values['priceSetID'] = $priceSet['id']; + } foreach ($lineItems as &$eachItem) { if (isset($this->_relatedObjects['membership']) - && is_array($this->_relatedObjects['membership']) - && array_key_exists($eachItem['membership_type_id'], $this->_relatedObjects['membership'])) { + && is_array($this->_relatedObjects['membership']) + && array_key_exists($eachItem['membership_type_id'], $this->_relatedObjects['membership'])) { $eachItem['join_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->join_date); $eachItem['start_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->start_date); $eachItem['end_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->end_date); } // This is actually used in conjunction with is_quick_config in the template & we should deprecate it. // However, that does create upgrade pain so would be better to be phased in. - $values['useForMember'] = !$priceSet['is_quick_config']; + $values['useForMember'] = empty($priceSet['is_quick_config']); } $values['lineItem'][0] = $lineItems; } @@ -2739,97 +3045,12 @@ public function _gatherMessageValues($input, &$values, $ids = array()) { } } else { - // event - $eventParams = array( - 'id' => $this->_relatedObjects['event']->id, - ); - $values['event'] = array(); - - CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); - // add custom fields for event - $eventGroupTree = CRM_Core_BAO_CustomGroup::getTree('Event', $this->_relatedObjects['event'], $this->_relatedObjects['event']->id); - - $eventCustomGroup = array(); - foreach ($eventGroupTree as $key => $group) { - if ($key === 'info') { - continue; - } - - foreach ($group['fields'] as $k => $customField) { - $groupLabel = $group['title']; - if (!empty($customField['customValue'])) { - foreach ($customField['customValue'] as $customFieldValues) { - $eventCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); - } - } - } - } - $values['event']['customGroup'] = $eventCustomGroup; - - //get participant details - $participantParams = array( - 'id' => $this->_relatedObjects['participant']->id, - ); - - $values['participant'] = array(); - - CRM_Event_BAO_Participant::getValues($participantParams, $values['participant'], $participantIds); - // add custom fields for event - $participantGroupTree = CRM_Core_BAO_CustomGroup::getTree('Participant', $this->_relatedObjects['participant'], $this->_relatedObjects['participant']->id); - $participantCustomGroup = array(); - foreach ($participantGroupTree as $key => $group) { - if ($key === 'info') { - continue; - } - - foreach ($group['fields'] as $k => $customField) { - $groupLabel = $group['title']; - if (!empty($customField['customValue'])) { - foreach ($customField['customValue'] as $customFieldValues) { - $participantCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); - } - } - } - } - $values['participant']['customGroup'] = $participantCustomGroup; - - //get location details - $locationParams = array( - 'entity_id' => $this->_relatedObjects['event']->id, - 'entity_table' => 'civicrm_event', - ); - $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); - - $ufJoinParams = array( - 'entity_table' => 'civicrm_event', - 'entity_id' => $ids['event'], - 'module' => 'CiviEvent', - ); - - list($custom_pre_id, - $custom_post_ids - ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); - - $values['custom_pre_id'] = $custom_pre_id; - $values['custom_post_id'] = $custom_post_ids; - - // set lineItem for event contribution - if ($this->id) { - $participantIds = CRM_Event_BAO_Participant::getParticipantIds($this->id); - if (!empty($participantIds)) { - foreach ($participantIds as $pIDs) { - $lineItem = CRM_Price_BAO_LineItem::getLineItems($pIDs); - if (!CRM_Utils_System::isNull($lineItem)) { - $values['lineItem'][] = $lineItem; - } - } - } - } + $values = array_merge($values, $this->loadEventMessageTemplateParams((int) $ids['event'], (int) $this->_relatedObjects['participant']->id, $this->id)); } - $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', $this, $this->id); + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', NULL, $this->id); - $customGroup = array(); + $customGroup = []; foreach ($groupTree as $key => $group) { if ($key === 'info') { continue; @@ -2846,6 +3067,8 @@ public function _gatherMessageValues($input, &$values, $ids = array()) { } $values['customGroup'] = $customGroup; + $values['is_pay_later'] = $this->is_pay_later; + return $values; } @@ -2869,6 +3092,10 @@ public function _gatherMessageValues($input, &$values, $ids = array()) { * @return mixed */ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessageText = TRUE) { + // @todo - this should have a better separation of concerns - ie. + // gatherMessageValues should build an array of values to be assigned to the template + // and this function should assign them (assigning null if not set). + // the way the pcpParams & honor Params section works is a baby-step towards this. $template = CRM_Core_Smarty::singleton(); $template->assign('first_name', $this->_relatedObjects['contact']->first_name); $template->assign('last_name', $this->_relatedObjects['contact']->last_name); @@ -2881,27 +3108,31 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa //assign honor information to receipt message $softRecord = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($this->id); + $honorParams = [ + 'soft_credit_type' => NULL, + 'honor_block_is_active' => NULL, + ]; if (isset($softRecord['soft_credit'])) { //if id of contribution page is present if (!empty($values['id'])) { - $values['honor'] = array( - 'honor_profile_values' => array(), + $values['honor'] = [ + 'honor_profile_values' => [], 'honor_profile_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFJoin', $values['id'], 'uf_group_id', 'entity_id'), 'honor_id' => $softRecord['soft_credit'][1]['contact_id'], - ); + ]; - $template->assign('soft_credit_type', $softRecord['soft_credit'][1]['soft_credit_type_label']); - $template->assign('honor_block_is_active', CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFJoin', $values['id'], 'is_active', 'entity_id')); + $honorParams['soft_credit_type'] = $softRecord['soft_credit'][1]['soft_credit_type_label']; + $honorParams['honor_block_is_active'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFJoin', $values['id'], 'is_active', 'entity_id'); } else { //offline contribution - $softCreditTypes = $softCredits = array(); + $softCreditTypes = $softCredits = []; foreach ($softRecord['soft_credit'] as $key => $softCredit) { $softCreditTypes[$key] = $softCredit['soft_credit_type_label']; - $softCredits[$key] = array( + $softCredits[$key] = [ 'Name' => $softCredit['contact_name'], 'Amount' => CRM_Utils_Money::format($softCredit['amount'], $softCredit['currency']), - ); + ]; } $template->assign('softCreditTypes', $softCreditTypes); $template->assign('softCredits', $softCredits); @@ -2928,25 +3159,35 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa $values['amount'] = $this->total_amount; } - // add the new contribution values + $pcpParams = [ + 'pcpBlock' => NULL, + 'pcp_display_in_roll' => NULL, + 'pcp_roll_nickname' => NULL, + 'pcp_personal_note' => NULL, + 'title' => NULL, + ]; + if (strtolower($this->_component) == 'contribute') { //PCP Info $softDAO = new CRM_Contribute_DAO_ContributionSoft(); $softDAO->contribution_id = $this->id; if ($softDAO->find(TRUE)) { - $template->assign('pcpBlock', TRUE); - $template->assign('pcp_display_in_roll', $softDAO->pcp_display_in_roll); - $template->assign('pcp_roll_nickname', $softDAO->pcp_roll_nickname); - $template->assign('pcp_personal_note', $softDAO->pcp_personal_note); + $pcpParams['pcpBlock'] = TRUE; + $pcpParams['pcp_display_in_roll'] = $softDAO->pcp_display_in_roll; + $pcpParams['pcp_roll_nickname'] = $softDAO->pcp_roll_nickname; + $pcpParams['pcp_personal_note'] = $softDAO->pcp_personal_note; //assign the pcp page title for email subject $pcpDAO = new CRM_PCP_DAO_PCP(); $pcpDAO->id = $softDAO->pcp_id; if ($pcpDAO->find(TRUE)) { - $template->assign('title', $pcpDAO->title); + $pcpParams['title'] = $pcpDAO->title; } } } + foreach (array_merge($honorParams, $pcpParams) as $templateKey => $templateValue) { + $template->assign($templateKey, $templateValue); + } if ($this->financial_type_id) { $values['financial_type_id'] = $this->financial_type_id; @@ -2976,7 +3217,7 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa if ($this->_component == 'event') { $template->assign('title', $values['event']['title']); $participantRoles = CRM_Event_PseudoConstant::participantRole(); - $viewRoles = array(); + $viewRoles = []; foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $this->_relatedObjects['participant']->role_id) as $k => $v) { $viewRoles[] = $participantRoles[$v]; } @@ -2992,13 +3233,13 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa $isTest = TRUE; } - $values['params'] = array(); + $values['params'] = []; //to get email of primary participant. $primaryEmail = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Email', $this->_relatedObjects['participant']->contact_id, 'email', 'contact_id'); - $primaryAmount[] = array( + $primaryAmount[] = [ 'label' => $this->_relatedObjects['participant']->fee_level . ' - ' . $primaryEmail, 'amount' => $this->_relatedObjects['participant']->fee_amount, - ); + ]; //build an array of cId/pId of participants $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($this->_relatedObjects['participant']->id, NULL, $this->_relatedObjects['contact']->id, $isTest, TRUE); unset($additionalIDs[$this->_relatedObjects['participant']->id]); @@ -3009,7 +3250,7 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa //set additionalParticipant true $values['params']['additionalParticipant'] = TRUE; foreach ($additionalIDs as $pId => $cId) { - $amount = array(); + $amount = []; //to change the status pending to completed $additional = new CRM_Event_DAO_Participant(); $additional->id = $pId; @@ -3023,13 +3264,15 @@ public function _assignMessageVariablesToTemplate(&$values, $input, $returnMessa if (!$additionalParticipantInfo) { $additionalParticipantInfo = CRM_Contact_BAO_Contact::displayName($additional->contact_id); } - $amount[0] = array('label' => $additional->fee_level, 'amount' => $additional->fee_amount); - $primaryAmount[] = array( + $amount[0] = [ + 'label' => $additional->fee_level, + 'amount' => $additional->fee_amount, + ]; + $primaryAmount[] = [ 'label' => $additional->fee_level . ' - ' . $additionalParticipantInfo, 'amount' => $additional->fee_amount, - ); + ]; $additional->save(); - $additional->free(); $template->assign('amount', $amount); CRM_Event_BAO_Event::sendMail($cId, $values, $pId, $isTest, $returnMessageText); } @@ -3073,7 +3316,7 @@ public static function isCancelSubscriptionSupported($contributionId, $isNotCanc $cacheKeyString = "$contributionId"; $cacheKeyString .= $isNotCancelled ? '_1' : '_0'; - static $supportsCancel = array(); + static $supportsCancel = []; if (!array_key_exists($cacheKeyString, $supportsCancel)) { $supportsCancel[$cacheKeyString] = FALSE; @@ -3106,9 +3349,9 @@ public static function isSubscriptionCancelled($contributionId) { FROM civicrm_contribution_recur cr LEFT JOIN civicrm_contribution con ON ( cr.id = con.contribution_recur_id ) WHERE con.id = %1 LIMIT 1"; - $params = array(1 => array($contributionId, 'Integer')); + $params = [1 => [$contributionId, 'Integer']]; $statusId = CRM_Core_DAO::singleValueQuery($sql, $params); - $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId); + $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId, 'name'); if ($status == 'Cancelled') { return TRUE; } @@ -3124,12 +3367,12 @@ public static function isSubscriptionCancelled($contributionId) { * * @param array $financialTrxnValues * - * @return null|object + * @return null|\CRM_Core_BAO_FinancialTrxn */ public static function recordFinancialAccounts(&$params, $financialTrxnValues = NULL) { $skipRecords = $update = $return = $isRelatedId = FALSE; - $additionalParticipantId = array(); + $additionalParticipantId = []; $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); $contributionStatus = empty($params['contribution_status_id']) ? NULL : $contributionStatuses[$params['contribution_status_id']]; @@ -3168,19 +3411,19 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = $statusId = $params['contribution']->contribution_status_id; // CRM-13964 partial payment if ($contributionStatus == 'Partially paid' - && !empty($params['partial_payment_total']) && !empty($params['partial_amount_pay']) + && !empty($params['partial_payment_total']) && !empty($params['partial_amount_to_pay']) ) { - $partialAmtPay = CRM_Utils_Rule::cleanMoney($params['partial_amount_pay']); + $partialAmtPay = CRM_Utils_Rule::cleanMoney($params['partial_amount_to_pay']); $partialAmtTotal = CRM_Utils_Rule::cleanMoney($params['partial_payment_total']); - $fromFinancialAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['financial_type_id'], 'Accounts Receivable Account is'); + $fromFinancialAccountId = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($params['financial_type_id'], 'Accounts Receivable Account is'); $statusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); $params['total_amount'] = $partialAmtPay; $balanceTrxnInfo = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($params['contribution']->id, $params['financial_type_id']); if (empty($balanceTrxnInfo['trxn_id'])) { // create new balance transaction record - $toFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['financial_type_id'], 'Accounts Receivable Account is'); + $toFinancialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($params['financial_type_id'], 'Accounts Receivable Account is'); $balanceTrxnParams['total_amount'] = $partialAmtTotal; $balanceTrxnParams['to_financial_account_id'] = $toFinancialAccount; @@ -3216,10 +3459,10 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = !($contributionStatus == 'Pending' && !$params['contribution']->is_pay_later) ) { $skipRecords = TRUE; - $pendingStatus = array( + $pendingStatus = [ 'Pending', 'In Progress', - ); + ]; if (in_array($contributionStatus, $pendingStatus)) { $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship( $params['financial_type_id'], @@ -3228,17 +3471,17 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = } elseif (!empty($params['payment_processor'])) { $params['to_financial_account_id'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['payment_processor'], NULL, 'civicrm_payment_processor'); - $params['payment_instrument_id'] = civicrm_api3('PaymentProcessor', 'getvalue', array( + $params['payment_instrument_id'] = civicrm_api3('PaymentProcessor', 'getvalue', [ 'id' => $params['payment_processor'], 'return' => 'payment_instrument_id', - )); + ]); } elseif (!empty($params['payment_instrument_id'])) { $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($params['payment_instrument_id']); } else { $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); - $queryParams = array(1 => array($relationTypeId, 'Integer')); + $queryParams = [1 => [$relationTypeId, 'Integer']]; $params['to_financial_account_id'] = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams); } @@ -3247,7 +3490,7 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = $totalAmount = $params['total_amount'] = $params['prevContribution']->total_amount; } //build financial transaction params - $trxnParams = array( + $trxnParams = [ 'contribution_id' => $params['contribution']->id, 'to_financial_account_id' => $params['to_financial_account_id'], 'trxn_date' => !empty($params['contribution']->receive_date) ? $params['contribution']->receive_date : date('YmdHis'), @@ -3256,12 +3499,15 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = 'net_amount' => CRM_Utils_Array::value('net_amount', $params, $totalAmount), 'currency' => $params['contribution']->currency, 'trxn_id' => $params['contribution']->trxn_id, + // @todo - this is getting the status id from the contribution - that is BAD - ie the contribution could be partially + // paid but each payment is completed. The work around is to pass in the status_id in the trxn_params but + // this should really default to completed (after discussion). 'status_id' => $statusId, 'payment_instrument_id' => CRM_Utils_Array::value('payment_instrument_id', $params, $params['contribution']->payment_instrument_id), 'check_number' => CRM_Utils_Array::value('check_number', $params), 'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $params), 'card_type_id' => CRM_Utils_Array::value('card_type_id', $params), - ); + ]; if ($contributionStatus == 'Refunded' || $contributionStatus == 'Chargeback' || $contributionStatus == 'Cancelled') { $trxnParams['trxn_date'] = !empty($params['contribution']->cancel_date) ? $params['contribution']->cancel_date : date('YmdHis'); if (isset($params['refund_trxn_id'])) { @@ -3321,12 +3567,12 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = if (!empty($params['revenue_recognition_date']) || $params['prevContribution']->revenue_recognition_date) { $accountRelationship = 'Deferred Revenue Account is'; } - $oldFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['prevContribution']->financial_type_id, $accountRelationship); - $newFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['financial_type_id'], $accountRelationship); + $oldFinancialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($params['prevContribution']->financial_type_id, $accountRelationship); + $newFinancialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($params['financial_type_id'], $accountRelationship); if ($oldFinancialAccount != $newFinancialAccount) { $params['total_amount'] = 0; if (in_array($params['contribution']->contribution_status_id, $pendingStatus)) { - $params['trxnParams']['to_financial_account_id'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount( + $params['trxnParams']['to_financial_account_id'] = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship( $params['prevContribution']->financial_type_id, $accountRelationship); } else { @@ -3365,48 +3611,14 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = // change Payment Instrument for a Completed contribution // first handle special case when contribution is changed from Pending to Completed status when initial payment // instrument is null and now new payment instrument is added along with the payment + if (!$params['contribution']->payment_instrument_id) { + $params['contribution']->find(TRUE); + } $params['trxnParams']['payment_instrument_id'] = $params['contribution']->payment_instrument_id; $params['trxnParams']['check_number'] = CRM_Utils_Array::value('check_number', $params); - if (array_key_exists('payment_instrument_id', $params)) { - $params['trxnParams']['total_amount'] = -$trxnParams['total_amount']; - if (CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id) && - !CRM_Utils_System::isNull($params['contribution']->payment_instrument_id) - ) { - //check if status is changed from Pending to Completed - // do not update payment instrument changes for Pending to Completed - if (!($params['contribution']->contribution_status_id == array_search('Completed', $contributionStatuses) && - in_array($params['prevContribution']->contribution_status_id, $pendingStatus)) - ) { - // for all other statuses create new financial records - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - $params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount']; - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - $updated = TRUE; - } - } - elseif ((!CRM_Utils_System::isNull($params['contribution']->payment_instrument_id) || - !CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id)) && - $params['contribution']->payment_instrument_id != $params['prevContribution']->payment_instrument_id - ) { - // for any other payment instrument changes create new financial records - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - $params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount']; - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - $updated = TRUE; - } - elseif (!CRM_Utils_System::isNull($params['contribution']->check_number) && - $params['contribution']->check_number != $params['prevContribution']->check_number - ) { - // another special case when check number is changed, create new financial records - // create financial trxn with negative amount - $params['trxnParams']['check_number'] = $params['prevContribution']->check_number; - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - // create financial trxn with positive amount - $params['trxnParams']['check_number'] = $params['contribution']->check_number; - $params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount']; - self::updateFinancialAccounts($params, 'changePaymentInstrument'); - $updated = TRUE; - } + + if (self::isPaymentInstrumentChange($params, $pendingStatus)) { + $updated = CRM_Core_BAO_FinancialTrxn::updateFinancialAccountsOnPaymentInstrumentChange($params); } //if Change contribution amount @@ -3433,7 +3645,10 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = if (isset($params['refund_trxn_id'])) { $refundIDs = CRM_Core_BAO_FinancialTrxn::getRefundTransactionIDs($params['id']); if (!empty($refundIDs['financialTrxnId']) && $refundIDs['trxn_id'] != $params['refund_trxn_id']) { - civicrm_api3('FinancialTrxn', 'create', array('id' => $refundIDs['financialTrxnId'], 'trxn_id' => $params['refund_trxn_id'])); + civicrm_api3('FinancialTrxn', 'create', [ + 'id' => $refundIDs['financialTrxnId'], + 'trxn_id' => $params['refund_trxn_id'], + ]); } } $cardType = CRM_Utils_Array::value('card_type_id', $params); @@ -3450,7 +3665,7 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = $trxnParams['card_type_id'] = CRM_Utils_Array::value('card_type_id', $params); $return = $financialTxn = CRM_Core_BAO_FinancialTrxn::create($trxnParams); $params['entity_id'] = $financialTxn->id; - if (empty($params['partial_payment_total']) && empty($params['partial_amount_pay'])) { + if (empty($params['partial_payment_total']) && empty($params['partial_amount_to_pay'])) { self::$_trxnIDs[] = $financialTxn->id; } } @@ -3463,12 +3678,12 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = // create batch entry if batch_id is passed and // ensure no batch entry is been made on 'Pending' or 'Failed' contribution, CRM-16611 if (!empty($params['batch_id']) && !empty($financialTxn)) { - $entityParams = array( + $entityParams = [ 'batch_id' => $params['batch_id'], 'entity_table' => 'civicrm_financial_trxn', 'entity_id' => $financialTxn->id, - ); - CRM_Batch_BAO_Batch::addBatchEntity($entityParams); + ]; + CRM_Batch_BAO_EntityBatch::create($entityParams); } // when a fee is charged @@ -3497,176 +3712,90 @@ public static function recordFinancialAccounts(&$params, $financialTrxnValues = * @param string $context * Update scenarios. * + * @todo stop passing $params by reference. It is unclear the purpose of doing this & + * adds unpredictability. + * */ public static function updateFinancialAccounts(&$params, $context = NULL) { $trxnID = NULL; $inputParams = $params; $isARefund = FALSE; - $currentContributionStatus = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id); - $previousContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($params['prevContribution']->contribution_status_id, 'name'); - if (($previousContributionStatus == 'Pending' - || $previousContributionStatus == 'In Progress') - && $currentContributionStatus == 'Completed' - && $context == 'changePaymentInstrument' - ) { - return; - } - if ((($previousContributionStatus == 'Partially paid' - && $currentContributionStatus == 'Completed') - || ($previousContributionStatus == 'Pending' && $params['prevContribution']->is_pay_later == TRUE - && $currentContributionStatus == 'Partially paid')) - && $context == 'changedStatus' - ) { - return; - } - if ($context == 'changedAmount' || $context == 'changeFinancialType') { - $params['trxnParams']['total_amount'] = $params['trxnParams']['net_amount'] = ($params['total_amount'] - $params['prevContribution']->total_amount); - } if ($context == 'changedStatus') { - if ($previousContributionStatus == 'Completed' - && (self::isContributionStatusNegative($params['contribution']->contribution_status_id)) - ) { - $isARefund = TRUE; - $params['trxnParams']['total_amount'] = -$params['total_amount']; - if (empty($params['contribution']->creditnote_id) || $params['contribution']->creditnote_id == "null") { - $creditNoteId = self::createCreditNoteId(); - CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $params['contribution']->id, 'creditnote_id', $creditNoteId); - } - } - elseif (($previousContributionStatus == 'Pending' - && $params['prevContribution']->is_pay_later) || $previousContributionStatus == 'In Progress' - ) { - $financialTypeID = CRM_Utils_Array::value('financial_type_id', $params) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id; - $arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeID, 'Accounts Receivable Account is'); - - if ($currentContributionStatus == 'Cancelled') { - $params['trxnParams']['to_financial_account_id'] = $arAccountId; - $params['trxnParams']['total_amount'] = -$params['total_amount']; - if (is_null($params['contribution']->creditnote_id) || $params['contribution']->creditnote_id == "null") { - $creditNoteId = self::createCreditNoteId(); - CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $params['contribution']->id, 'creditnote_id', $creditNoteId); - } - } - else { - $params['trxnParams']['from_financial_account_id'] = $arAccountId; - } - } - } - elseif ($context == 'changePaymentInstrument') { - $params['trxnParams']['net_amount'] = $params['trxnParams']['total_amount']; - $deferredFinancialAccount = CRM_Utils_Array::value('deferred_financial_account_id', $params); - if (empty($deferredFinancialAccount)) { - $deferredFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['prevContribution']->financial_type_id, 'Deferred Revenue Account is'); - } - $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($params['prevContribution']->id, 'DESC', FALSE, NULL, $deferredFinancialAccount); - if (!empty($lastFinancialTrxnId['financialTrxnId'])) { - if ($params['total_amount'] != $params['trxnParams']['total_amount']) { - $params['trxnParams']['to_financial_account_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialTrxn', $lastFinancialTrxnId['financialTrxnId'], 'to_financial_account_id'); - $params['trxnParams']['payment_instrument_id'] = $params['prevContribution']->payment_instrument_id; - } - else { - $params['trxnParams']['to_financial_account_id'] = $params['to_financial_account_id']; - $params['trxnParams']['payment_instrument_id'] = $params['contribution']->payment_instrument_id; - } + list($continue, $isARefund) = self::updateFinancialAccountsOnContributionStatusChange($params); + // @todo - it may be that this is always false & the parent function is just a confusing wrapper for the child fn. + if (!$continue) { + return; } } - if ($context == 'changedStatus') { - if (($previousContributionStatus == 'Pending' - || $previousContributionStatus == 'In Progress') - && ($currentContributionStatus == 'Completed') - ) { - if (empty($params['line_item'])) { - //CRM-15296 - //@todo - check with Joe regarding this situation - payment processors create pending transactions with no line items - // when creating recurring membership payment - there are 2 lines to comment out in contributonPageTest if fixed - // & this can be removed - return; - } - self::recordAlwaysAccountsReceivable($params['trxnParams'], $params); - $trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']); - $params['entity_id'] = self::$_trxnIDs[] = $trxn->id; - $query = "UPDATE civicrm_financial_item SET status_id = %1 WHERE entity_id = %2 and entity_table = 'civicrm_line_item'"; - $sql = "SELECT id, amount FROM civicrm_financial_item WHERE entity_id = %1 and entity_table = 'civicrm_line_item'"; - - $entityParams = array( - 'entity_table' => 'civicrm_financial_item', - ); - foreach ($params['line_item'] as $fieldId => $fields) { - foreach ($fields as $fieldValueId => $lineItemDetails) { - $fparams = array( - 1 => array(CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'), 'Integer'), - 2 => array($lineItemDetails['id'], 'Integer'), - ); - CRM_Core_DAO::executeQuery($query, $fparams); - $fparams = array( - 1 => array($lineItemDetails['id'], 'Integer'), - ); - $financialItem = CRM_Core_DAO::executeQuery($sql, $fparams); - while ($financialItem->fetch()) { - $entityParams['entity_id'] = $financialItem->id; - $entityParams['amount'] = $financialItem->amount; - foreach (self::$_trxnIDs as $tID) { - $entityParams['financial_trxn_id'] = $tID; - CRM_Financial_BAO_FinancialItem::createEntityTrxn($entityParams); - } - } - } - } - return; - } + if ($context == 'changedAmount' || $context == 'changeFinancialType') { + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['trxnParams']['total_amount'] = $params['trxnParams']['net_amount'] = ($params['total_amount'] - $params['prevContribution']->total_amount); } $trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']); + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. $params['entity_id'] = $trxn->id; - if ($context != 'changePaymentInstrument') { - $itemParams['entity_table'] = 'civicrm_line_item'; - $trxnIds['id'] = $params['entity_id']; - foreach ($params['line_item'] as $fieldId => $fields) { - foreach ($fields as $fieldValueId => $lineItemDetails) { - $prevFinancialItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($lineItemDetails['id']); - $receiveDate = CRM_Utils_Date::isoToMysql($params['prevContribution']->receive_date); - if ($params['contribution']->receive_date) { - $receiveDate = CRM_Utils_Date::isoToMysql($params['contribution']->receive_date); - } - $financialAccount = self::getFinancialAccountForStatusChangeTrxn($params, $prevFinancialItem['financial_account_id']); + $itemParams['entity_table'] = 'civicrm_line_item'; + $trxnIds['id'] = $params['entity_id']; + $previousLineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution']->id); + foreach ($params['line_item'] as $fieldId => $fields) { + foreach ($fields as $fieldValueId => $lineItemDetails) { + $prevFinancialItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($lineItemDetails['id']); + $receiveDate = CRM_Utils_Date::isoToMysql($params['prevContribution']->receive_date); + if ($params['contribution']->receive_date) { + $receiveDate = CRM_Utils_Date::isoToMysql($params['contribution']->receive_date); + } - $currency = $params['prevContribution']->currency; - if ($params['contribution']->currency) { - $currency = $params['contribution']->currency; - } + $financialAccount = self::getFinancialAccountForStatusChangeTrxn($params, CRM_Utils_Array::value('financial_account_id', $prevFinancialItem)); - $itemParams = array( - 'transaction_date' => $receiveDate, - 'contact_id' => $params['prevContribution']->contact_id, - 'currency' => $currency, - 'amount' => self::getFinancialItemAmountFromParams($inputParams, $context, $lineItemDetails, $isARefund), - 'description' => $prevFinancialItem['description'], - 'status_id' => $prevFinancialItem['status_id'], - 'financial_account_id' => $financialAccount, - 'entity_table' => 'civicrm_line_item', - 'entity_id' => $lineItemDetails['id'], - ); - $financialItem = CRM_Financial_BAO_FinancialItem::create($itemParams, NULL, $trxnIds); - $params['line_item'][$fieldId][$fieldValueId]['deferred_line_total'] = $itemParams['amount']; - $params['line_item'][$fieldId][$fieldValueId]['financial_item_id'] = $financialItem->id; - - if ($lineItemDetails['tax_amount'] && $lineItemDetails['tax_amount'] !== 'null') { - $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $taxTerm = CRM_Utils_Array::value('tax_term', $invoiceSettings); - $itemParams['amount'] = self::getMultiplier($params['contribution']->contribution_status_id, $context) * $lineItemDetails['tax_amount']; - $itemParams['description'] = $taxTerm; + $currency = $params['prevContribution']->currency; + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + if ($params['contribution']->currency) { + $currency = $params['contribution']->currency; + } + $previousLineItemTotal = CRM_Utils_Array::value('line_total', CRM_Utils_Array::value($fieldValueId, $previousLineItems), 0); + $itemParams = [ + 'transaction_date' => $receiveDate, + 'contact_id' => $params['prevContribution']->contact_id, + 'currency' => $currency, + 'amount' => self::getFinancialItemAmountFromParams($inputParams, $context, $lineItemDetails, $isARefund, $previousLineItemTotal), + 'description' => CRM_Utils_Array::value('description', $prevFinancialItem), + 'status_id' => $prevFinancialItem['status_id'], + 'financial_account_id' => $financialAccount, + 'entity_table' => 'civicrm_line_item', + 'entity_id' => $lineItemDetails['id'], + ]; + $financialItem = CRM_Financial_BAO_FinancialItem::create($itemParams, NULL, $trxnIds); + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. + $params['line_item'][$fieldId][$fieldValueId]['deferred_line_total'] = $itemParams['amount']; + $params['line_item'][$fieldId][$fieldValueId]['financial_item_id'] = $financialItem->id; + + if (($lineItemDetails['tax_amount'] && $lineItemDetails['tax_amount'] !== 'null') || ($context == 'changeFinancialType')) { + $taxAmount = (float) $lineItemDetails['tax_amount']; + if ($context == 'changeFinancialType' && $lineItemDetails['tax_amount'] === 'null') { + // reverse the Sale Tax amount if there is no tax rate associated with new Financial Type + $taxAmount = CRM_Utils_Array::value('tax_amount', CRM_Utils_Array::value($fieldValueId, $previousLineItems), 0); + } + elseif ($previousLineItemTotal != $lineItemDetails['line_total']) { + $taxAmount -= CRM_Utils_Array::value('tax_amount', CRM_Utils_Array::value($fieldValueId, $previousLineItems), 0); + } + if ($taxAmount != 0) { + $itemParams['amount'] = self::getMultiplier($params['contribution']->contribution_status_id, $context) * $taxAmount; + $itemParams['description'] = CRM_Invoicing_Utils::getTaxTerm(); if ($lineItemDetails['financial_type_id']) { - $itemParams['financial_account_id'] = self::getFinancialAccountId($lineItemDetails['financial_type_id']); + $itemParams['financial_account_id'] = CRM_Financial_BAO_FinancialAccount::getSalesTaxFinancialAccount($lineItemDetails['financial_type_id']); } CRM_Financial_BAO_FinancialItem::create($itemParams, NULL, $trxnIds); } } } } + if ($context == 'changeFinancialType') { + // @todo we should stop passing $params by reference - splitting this out would be a step towards that. $params['skipLineItem'] = FALSE; foreach ($params['line_item'] as &$lineItems) { foreach ($lineItems as &$line) { @@ -3674,14 +3803,7 @@ public static function updateFinancialAccounts(&$params, $context = NULL) { } } } - if ($context == 'changePaymentInstrument') { - // store financial item Proportionaly. - $trxnParams = array( - 'total_amount' => $trxn->total_amount, - 'contribution_id' => $params['contribution']->id, - ); - self::assignProportionalLineItems($trxnParams, $trxn->id, $params['prevContribution']->total_amount); - } + CRM_Core_BAO_FinancialTrxn::createDeferredTrxn(CRM_Utils_Array::value('line_item', $params), $params['contribution'], TRUE, $context); } @@ -3695,7 +3817,7 @@ public static function updateFinancialAccounts(&$params, $context = NULL) { * @return bool */ public static function isContributionStatusNegative($status_id) { - $reversalStatuses = array('Cancelled', 'Chargeback', 'Refunded'); + $reversalStatuses = ['Cancelled', 'Chargeback', 'Refunded']; return in_array(CRM_Contribute_PseudoConstant::contributionStatus($status_id, 'name'), $reversalStatuses); } @@ -3721,22 +3843,23 @@ public static function checkStatusValidation($values, &$fields, &$errors) { } } $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - $checkStatus = array( - 'Cancelled' => array('Completed', 'Refunded'), - 'Completed' => array('Cancelled', 'Refunded', 'Chargeback'), - 'Pending' => array('Cancelled', 'Completed', 'Failed', 'Partially paid'), - 'In Progress' => array('Cancelled', 'Completed', 'Failed'), - 'Refunded' => array('Cancelled', 'Completed'), - 'Partially paid' => array('Completed'), - ); + $checkStatus = [ + 'Cancelled' => ['Completed', 'Refunded'], + 'Completed' => ['Cancelled', 'Refunded', 'Chargeback'], + 'Pending' => ['Cancelled', 'Completed', 'Failed', 'Partially paid'], + 'In Progress' => ['Cancelled', 'Completed', 'Failed'], + 'Refunded' => ['Cancelled', 'Completed'], + 'Partially paid' => ['Completed'], + 'Pending refund' => ['Completed', 'Refunded'], + ]; if (!in_array($contributionStatuses[$fields['contribution_status_id']], - CRM_Utils_Array::value($contributionStatuses[$values['contribution_status_id']], $checkStatus, array())) + CRM_Utils_Array::value($contributionStatuses[$values['contribution_status_id']], $checkStatus, [])) ) { - $errors['contribution_status_id'] = ts("Cannot change contribution status from %1 to %2.", array( + $errors['contribution_status_id'] = ts("Cannot change contribution status from %1 to %2.", [ 1 => $contributionStatuses[$values['contribution_status_id']], 2 => $contributionStatuses[$fields['contribution_status_id']], - )); + ]); } } @@ -3760,17 +3883,18 @@ public static function deleteContactContribution($contactId) { /** * Get options for a given contribution field. - * @see CRM_Core_DAO::buildOptions * * @param string $fieldName * @param string $context see CRM_Core_DAO::buildOptionsContext. - * @param array $props whatever is known about this dao object. + * @param array $props whatever is known about this dao object. * * @return array|bool + * @see CRM_Core_DAO::buildOptions + * */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { + public static function buildOptions($fieldName, $context = NULL, $props = []) { $className = __CLASS__; - $params = array(); + $params = []; if (isset($props['orderColumn'])) { $params['orderColumn'] = $props['orderColumn']; } @@ -3780,10 +3904,10 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( $className = 'CRM_Contribute_BAO_ContributionPage'; // Filter results by contribution page if (!empty($props['contribution_page_id'])) { - $page = civicrm_api('contribution_page', 'getsingle', array( + $page = civicrm_api('contribution_page', 'getsingle', [ 'version' => 3, 'id' => ($props['contribution_page_id']), - )); + ]); $types = (array) CRM_Utils_Array::value('payment_processor', $page, 0); $params['condition'] = 'id IN (' . implode(',', $types) . ')'; } @@ -3795,6 +3919,11 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( $fieldName = 'soft_credit_type_id'; $params['condition'] = "v.name IN ('in_honor_of','in_memory_of')"; break; + + case 'contribution_status_id': + if ($context !== 'validate') { + $params['condition'] = "v.name <> 'Template'"; + } } return CRM_Core_PseudoConstant::get($className, $fieldName, $params, $context); } @@ -3820,212 +3949,27 @@ public static function validateFinancialType($financialTypeId, $relationName = ' return FALSE; } - - /** - * Function to record additional payment for partial and refund contributions. - * - * @param int $contributionId - * is the invoice contribution id (got created after processing participant payment). - * @param array $trxnsData - * to take user provided input of transaction details. - * @param string $paymentType - * 'owed' for purpose of recording partial payments, 'refund' for purpose of recording refund payments. - * @param int $participantId - * @param bool $updateStatus - * - * @return null|object - */ - public static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL, $updateStatus = TRUE) { - $statusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); - $getInfoOf['id'] = $contributionId; - $defaults = array(); - $contributionDAO = CRM_Contribute_BAO_Contribution::retrieve($getInfoOf, $defaults, CRM_Core_DAO::$_nullArray); - if (!$participantId) { - $participantId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $contributionId, 'participant_id', 'contribution_id'); - } - - // build params for recording financial trxn entry - $params['contribution'] = $contributionDAO; - $params = array_merge($defaults, $params); - $params['skipLineItem'] = TRUE; - $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis'); - $arAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contributionDAO->financial_type_id, 'Accounts Receivable Account is'); - if ($paymentType == 'owed') { - $params['partial_payment_total'] = $contributionDAO->total_amount; - $params['partial_amount_pay'] = $trxnsData['total_amount']; - $trxnsData['net_amount'] = !empty($trxnsData['net_amount']) ? $trxnsData['net_amount'] : $trxnsData['total_amount']; - $params['pan_truncation'] = CRM_Utils_Array::value('pan_truncation', $trxnsData); - $params['card_type_id'] = CRM_Utils_Array::value('card_type_id', $trxnsData); - - // record the entry - $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData); - $toFinancialAccount = $arAccountId; - $trxnId = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId, $contributionDAO->financial_type_id); - if (!empty($trxnId)) { - $trxnId = $trxnId['trxn_id']; - } - elseif (!empty($contributionDAO->payment_instrument_id)) { - $trxnId = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contributionDAO->payment_instrument_id); - } - else { - $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); - $queryParams = array(1 => array($relationTypeId, 'Integer')); - $trxnId = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams); - } - - // update statuses - // criteria for updates contribution total_amount == financial_trxns of partial_payments - $sql = "SELECT SUM(ft.total_amount) as sum_of_payments, SUM(ft.net_amount) as net_amount_total -FROM civicrm_financial_trxn ft -LEFT JOIN civicrm_entity_financial_trxn eft - ON (ft.id = eft.financial_trxn_id) -WHERE eft.entity_table = 'civicrm_contribution' - AND eft.entity_id = {$contributionId} - AND ft.to_financial_account_id != {$toFinancialAccount} - AND ft.status_id = {$statusId} -"; - $query = CRM_Core_DAO::executeQuery($sql); - $query->fetch(); - $sumOfPayments = $query->sum_of_payments; - - // update statuses - if ($contributionDAO->total_amount == $sumOfPayments) { - // update contribution status and - // clean cancel info (if any) if prev. contribution was updated in case of 'Refunded' => 'Completed' - $contributionDAO->contribution_status_id = $statusId; - $contributionDAO->cancel_date = 'null'; - $contributionDAO->cancel_reason = NULL; - $netAmount = !empty($trxnsData['net_amount']) ? NULL : $trxnsData['total_amount']; - $contributionDAO->net_amount = $query->net_amount_total + $netAmount; - $contributionDAO->fee_amount = $contributionDAO->total_amount - $contributionDAO->net_amount; - $contributionDAO->save(); - - //Change status of financial record too - $financialTrxn->status_id = $statusId; - $financialTrxn->save(); - - // note : not using the self::add method, - // the reason because it performs 'status change' related code execution for financial records - // which in 'Partial Paid' => 'Completed' is not useful, instead specific financial record updates - // are coded below i.e. just updating financial_item status to 'Paid' - - if ($participantId) { - // update participant status - $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); - $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId); - foreach ($ids as $val) { - $participantUpdate['id'] = $val; - $participantUpdate['status_id'] = array_search('Registered', $participantStatuses); - CRM_Event_BAO_Participant::add($participantUpdate); - } - } - - // update financial item statuses - $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id'); - $paidStatus = array_search('Paid', $financialItemStatus); - - $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId); - $sqlFinancialItemUpdate = " -UPDATE civicrm_financial_item fi - LEFT JOIN civicrm_entity_financial_trxn eft - ON (eft.entity_id = fi.id AND eft.entity_table = 'civicrm_financial_item') -SET status_id = {$paidStatus} -WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']}) -"; - CRM_Core_DAO::executeQuery($sqlFinancialItemUpdate); - } - } - elseif ($paymentType == 'refund') { - $trxnsData['total_amount'] = -$trxnsData['total_amount']; - $trxnsData['from_financial_account_id'] = $arAccountId; - $trxnsData['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); - // record the entry - $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData); - - // note : not using the self::add method, - // the reason because it performs 'status change' related code execution for financial records - // which in 'Pending Refund' => 'Completed' is not useful, instead specific financial record updates - // are coded below i.e. just updating financial_item status to 'Paid' - if ($updateStatus) { - $contributionDetails = CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $statusId); - } - // add financial item entry - $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id'); - $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contributionDAO->id); - if (!empty($lineItems)) { - foreach ($lineItems as $lineItemId => $lineItemValue) { - $paid = $lineItemValue['line_total'] * ($financialTrxn->total_amount / $contributionDAO->total_amount); - $addFinancialEntry = array( - 'transaction_date' => $financialTrxn->trxn_date, - 'contact_id' => $contributionDAO->contact_id, - 'amount' => round($paid, 2), - 'status_id' => array_search('Paid', $financialItemStatus), - 'entity_id' => $lineItemId, - 'entity_table' => 'civicrm_line_item', - ); - $trxnIds['id'] = $financialTrxn->id; - CRM_Financial_BAO_FinancialItem::create($addFinancialEntry, NULL, $trxnIds); - } - } - if ($participantId) { - // update participant status - $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); - $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId); - foreach ($ids as $val) { - $participantUpdate['id'] = $val; - $participantUpdate['status_id'] = array_search('Registered', $participantStatuses); - CRM_Event_BAO_Participant::add($participantUpdate); - } - } - } - - // activity creation - if (!empty($financialTrxn)) { - if ($participantId) { - $inputParams['id'] = $participantId; - $values = array(); - $ids = array(); - $component = 'event'; - $entityObj = CRM_Event_BAO_Participant::getValues($inputParams, $values, $ids); - $entityObj = $entityObj[$participantId]; - } - else { - $entityObj = $contributionDAO; - $component = 'contribution'; - } - $activityType = ($paymentType == 'refund') ? 'Refund' : 'Payment'; - - self::addActivityForPayment($entityObj, $financialTrxn, $activityType, $component, $contributionId); - return $financialTrxn; - } - - } - /** - * @param $entityObj - * @param $trxnObj + * @param int $targetCid * @param $activityType - * @param $component + * @param string $title * @param int $contributionId + * @param string $totalAmount + * @param string $currency + * @param string $trxn_date * - * @throws CRM_Core_Exception + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ - public static function addActivityForPayment($entityObj, $trxnObj, $activityType, $component, $contributionId) { - if ($component == 'event') { - $title = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Event', $entityObj->event_id, 'title'); - } - else { - $title = ts('Contribution'); - } - $paymentAmount = CRM_Utils_Money::format($trxnObj->total_amount, $trxnObj->currency); + public static function addActivityForPayment($targetCid, $activityType, $title, $contributionId, $totalAmount, $currency, $trxn_date) { + $paymentAmount = CRM_Utils_Money::format($totalAmount, $currency); $subject = "{$paymentAmount} - Offline {$activityType} for {$title}"; - $date = CRM_Utils_Date::isoToMysql($trxnObj->trxn_date); - $targetCid = $entityObj->contact_id; + $date = CRM_Utils_Date::isoToMysql($trxn_date); // source record id would be the contribution id $srcRecId = $contributionId; // activity params - $activityParams = array( + $activityParams = [ 'source_contact_id' => $targetCid, 'source_record_id' => $srcRecId, 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', $activityType), @@ -4033,7 +3977,7 @@ public static function addActivityForPayment($entityObj, $trxnObj, $activityType 'activity_date_time' => $date, 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), 'skipRecentView' => TRUE, - ); + ]; // create activity with target contacts $session = CRM_Core_Session::singleton(); @@ -4042,8 +3986,7 @@ public static function addActivityForPayment($entityObj, $trxnObj, $activityType $activityParams['source_contact_id'] = $id; $activityParams['target_contact_id'][] = $targetCid; } - // @todo use api. - CRM_Activity_BAO_Activity::create($activityParams); + civicrm_api3('Activity', 'create', $activityParams); } /** @@ -4056,10 +3999,9 @@ public static function addActivityForPayment($entityObj, $trxnObj, $activityType * * @return mixed */ - public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $usingLineTotal = FALSE) { + public static function getPaymentInfo($id, $component = 'contribution', $getTrxnInfo = FALSE, $usingLineTotal = FALSE) { + // @todo deprecate passing in component - always call with contribution. if ($component == 'event') { - $entity = 'participant'; - $entityTable = 'civicrm_participant'; $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_ParticipantPayment', $id, 'contribution_id', 'participant_id'); if (!$contributionId) { @@ -4072,10 +4014,11 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us } } } + elseif ($component == 'membership') { + $contributionId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $id, 'contribution_id', 'membership_id'); + } else { $contributionId = $id; - $entity = 'contribution'; - $entityTable = 'civicrm_contribution'; } $total = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId); @@ -4084,7 +4027,7 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId); $baseTrxnId = $baseTrxnId['financialTrxnId']; } - if (!CRM_Utils_Array::value('total_amount', $total) || $usingLineTotal) { + if (empty($total['total_amount']) || $usingLineTotal) { $total = CRM_Price_BAO_LineItem::getLineTotal($contributionId); } else { @@ -4092,30 +4035,38 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us $total = $total['total_amount']; } - $paymentBalance = CRM_Core_BAO_FinancialTrxn::getPartialPaymentWithType($id, $entity, FALSE, $total); - $contributionIsPayLater = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'is_pay_later'); + $paymentBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($contributionId, $total); - $financialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'financial_type_id'); - $feeFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeId, 'Expense Account is'); + $contribution = civicrm_api3('Contribution', 'getsingle', [ + 'id' => $contributionId, + 'return' => [ + 'currency', + 'is_pay_later', + 'contribution_status_id', + 'financial_type_id', + ], + ]); - if ($paymentBalance == 0 && $contributionIsPayLater) { - $paymentBalance = $total; - } + $info['payLater'] = $contribution['is_pay_later']; + $info['contribution_status'] = $contribution['contribution_status']; + $info['currency'] = $contribution['currency']; + + $financialTypeId = $contribution['financial_type_id']; + $feeFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeId, 'Expense Account is'); $info['total'] = $total; $info['paid'] = $total - $paymentBalance; $info['balance'] = $paymentBalance; $info['id'] = $id; $info['component'] = $component; - $info['payLater'] = $contributionIsPayLater; - $rows = array(); + $rows = []; if ($getTrxnInfo && $baseTrxnId) { // Need to exclude fee trxn rows so filter out rows where TO FINANCIAL ACCOUNT is expense account $sql = " SELECT GROUP_CONCAT(fa.`name`) as financial_account, ft.total_amount, ft.payment_instrument_id, - ft.trxn_date, ft.trxn_id, ft.status_id, ft.check_number, ft.currency, ft.pan_truncation, ft.card_type_id + ft.trxn_date, ft.trxn_id, ft.status_id, ft.check_number, ft.currency, ft.pan_truncation, ft.card_type_id, ft.id FROM civicrm_contribution con LEFT JOIN civicrm_entity_financial_trxn eft ON (eft.entity_id = con.id AND eft.entity_table = 'civicrm_contribution') @@ -4127,12 +4078,12 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us WHERE con.id = %1 AND ft.is_payment = 1 GROUP BY ft.id"; - $queryParams = array( - 1 => array($contributionId, 'Integer'), - 2 => array($feeFinancialAccount, 'Integer'), - ); + $queryParams = [ + 1 => [$contributionId, 'Integer'], + 2 => [$feeFinancialAccount, 'Integer'], + ]; $resultDAO = CRM_Core_DAO::executeQuery($sql, $queryParams); - $statuses = CRM_Contribute_PseudoConstant::contributionStatus(); + $statuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'label'); while ($resultDAO->fetch()) { $paidByLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $resultDAO->payment_instrument_id); @@ -4145,7 +4096,31 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us } $paidByLabel .= " ({$creditCardType}{$pantruncation})"; } - $val = array( + + // show payment edit link only for payments done via backoffice form + $paymentEditLink = ''; + if (empty($resultDAO->payment_processor_id) && CRM_Core_Permission::check('edit contributions')) { + $links = [ + CRM_Core_Action::UPDATE => [ + 'name' => "", + 'url' => 'civicrm/payment/edit', + 'class' => 'medium-popup', + 'qs' => "reset=1&id=%%id%%&contribution_id=%%contribution_id%%", + 'title' => ts('Edit Payment'), + ], + ]; + $paymentEditLink = CRM_Core_Action::formLink( + $links, + CRM_Core_Action::mask([CRM_Core_Permission::EDIT]), + [ + 'id' => $resultDAO->id, + 'contribution_id' => $contributionId, + ] + ); + } + + $val = [ + 'id' => $resultDAO->id, 'total_amount' => $resultDAO->total_amount, 'financial_type' => $resultDAO->financial_account, 'payment_instrument' => $paidByLabel, @@ -4153,7 +4128,8 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us 'trxn_id' => $resultDAO->trxn_id, 'status' => $statuses[$resultDAO->status_id], 'currency' => $resultDAO->currency, - ); + 'action' => $paymentEditLink, + ]; if ($paidByName == 'Check') { $val['check_number'] = $resultDAO->check_number; } @@ -4161,28 +4137,30 @@ public static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $us } $info['transaction'] = $rows; } + + $info['payment_links'] = self::getContributionPaymentLinks($id, $paymentBalance, $info['contribution_status']); return $info; } /** - * Get financial account id has 'Sales Tax Account is' account relationship with financial type. + * Get the outstanding balance on a contribution. * - * @param int $financialTypeId + * @param int $contributionId + * @param float $contributionTotal + * Optional amount to override the saved amount paid (e.g if calculating what it WILL be). * - * @return int - * Financial Account Id + * @return float */ - public static function getFinancialAccountId($financialTypeId) { - $accountRel = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")); - $searchParams = array( - 'entity_table' => 'civicrm_financial_type', - 'entity_id' => $financialTypeId, - 'account_relationship' => $accountRel, - ); - $result = array(); - CRM_Financial_BAO_FinancialTypeAccount::retrieve($searchParams, $result); + public static function getContributionBalance($contributionId, $contributionTotal = NULL) { + if ($contributionTotal === NULL) { + $contributionTotal = CRM_Price_BAO_LineItem::getLineTotal($contributionId); + } - return CRM_Utils_Array::value('financial_account_id', $result); + return CRM_Utils_Money::subtractCurrencies( + $contributionTotal, + CRM_Core_BAO_FinancialTrxn::getTotalPayments($contributionId, TRUE) ?: 0, + CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'currency') + ); } /** @@ -4196,6 +4174,11 @@ public static function getFinancialAccountId($financialTypeId) { public static function checkTaxAmount($params, $isLineItem = FALSE) { $taxRates = CRM_Core_PseudoConstant::getTaxRates(); + // This function should be only called after standardisation (removal of + // thousand separator & using a decimal point for cents separator. + // However, we don't know if that is always true :-( + // There is a deprecation notice tho :-) + $unknownIfMoneyIsClean = empty($params['skipCleanMoney']) && !$isLineItem; // Update contribution. if (!empty($params['id'])) { // CRM-19126 and CRM-19152 If neither total or financial_type_id are set on an update @@ -4207,7 +4190,7 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { $params['prevContribution'] = self::getOriginalContribution($params['id']); } - foreach (array('total_amount', 'financial_type_id', 'fee_amount') as $field) { + foreach (['total_amount', 'financial_type_id', 'fee_amount'] as $field) { if (!isset($params[$field])) { if ($field == 'total_amount' && $params['prevContribution']->tax_amount) { // Tax amount gets added back on later.... @@ -4227,7 +4210,7 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { // Assign tax Amount on update of contribution if (!empty($params['prevContribution']->tax_amount)) { $params['tax_amount'] = 'null'; - CRM_Price_BAO_LineItem::getLineItemArray($params, array($params['id'])); + CRM_Price_BAO_LineItem::getLineItemArray($params, [$params['id']]); foreach ($params['line_item'] as $setID => $priceField) { foreach ($priceField as $priceFieldID => $priceFieldValue) { $params['line_item'][$setID][$priceFieldID]['tax_amount'] = $params['tax_amount']; @@ -4242,12 +4225,12 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { empty($params['skipLineItem']) && !$isLineItem ) { $taxRateParams = $taxRates[$params['financial_type_id']]; - $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount(CRM_Utils_Array::value('total_amount', $params), $taxRateParams); + $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount(CRM_Utils_Array::value('total_amount', $params), $taxRateParams, $unknownIfMoneyIsClean); $params['tax_amount'] = round($taxAmount['tax_amount'], 2); // Get Line Item on update of contribution if (isset($params['id'])) { - CRM_Price_BAO_LineItem::getLineItemArray($params, array($params['id'])); + CRM_Price_BAO_LineItem::getLineItemArray($params, [$params['id']]); } else { CRM_Price_BAO_LineItem::getLineItemArray($params); @@ -4261,7 +4244,7 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { } elseif (isset($params['api.line_item.create'])) { // Update total amount of contribution using lineItem - $taxAmountArray = array(); + $taxAmountArray = []; foreach ($params['api.line_item.create'] as $key => $value) { if (isset($value['financial_type_id']) && array_key_exists($value['financial_type_id'], $taxRates)) { $taxRate = $taxRates[$value['financial_type_id']]; @@ -4276,7 +4259,7 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { // update line item of contrbution if (isset($params['financial_type_id']) && array_key_exists($params['financial_type_id'], $taxRates) && $isLineItem) { $taxRate = $taxRates[$params['financial_type_id']]; - $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount($params['line_total'], $taxRate); + $taxAmount = CRM_Contribute_BAO_Contribution_Utils::calculateTaxAmount($params['line_total'], $taxRate, $unknownIfMoneyIsClean); $params['tax_amount'] = round($taxAmount['tax_amount'], 2); } } @@ -4286,28 +4269,28 @@ public static function checkTaxAmount($params, $isLineItem = FALSE) { /** * Check financial type validation on update of a contribution. * - * @param Integer $financialTypeId + * @param int $financialTypeId * Value of latest Financial Type. * - * @param Integer $contributionId + * @param int $contributionId * Contribution Id. * * @param array $errors * List of errors. * - * @return bool + * @return void */ public static function checkFinancialTypeChange($financialTypeId, $contributionId, &$errors) { if (!empty($financialTypeId)) { $oldFinancialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'financial_type_id'); if ($oldFinancialTypeId == $financialTypeId) { - return FALSE; + return; } } $sql = 'SELECT financial_type_id FROM civicrm_line_item WHERE contribution_id = %1 GROUP BY financial_type_id;'; - $params = array( - '1' => array($contributionId, 'Integer'), - ); + $params = [ + '1' => [$contributionId, 'Integer'], + ]; $result = CRM_Core_DAO::executeQuery($sql, $params); if ($result->N > 1) { $errors['financial_type_id'] = ts('One or more line items have a different financial type than the contribution. Editing the financial type is not yet supported in this situation.'); @@ -4378,7 +4361,7 @@ public static function updateRelatedPledge( if ($updatePledgePaymentStatus) { CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, - array($pledgePaymentID), + [$pledgePaymentID], $contribution_status_id, NULL, $total_amount, @@ -4395,60 +4378,12 @@ public static function updateRelatedPledge( * @param string $alias of civicrm_contribution * * @return array|null + * @deprecated + * */ public static function computeStats($stat, $sql, $alias = NULL) { - $mode = $median = array(); - switch ($stat) { - case 'mode': - $modeDAO = CRM_Core_DAO::executeQuery($sql); - while ($modeDAO->fetch()) { - if ($modeDAO->civicrm_contribution_total_amount_count > 1) { - $mode[] = CRM_Utils_Money::format($modeDAO->amount, $modeDAO->currency); - } - else { - $mode[] = 'N/A'; - } - } - return $mode; - - case 'median': - $currencies = CRM_Core_OptionGroup::values('currencies_enabled'); - foreach ($currencies as $currency => $val) { - $midValue = 0; - $where = "AND {$alias}.currency = '{$currency}'"; - $rowCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) as count {$sql} {$where}"); - - $even = FALSE; - $offset = 1; - $medianRow = floor($rowCount / 2); - if ($rowCount % 2 == 0 && !empty($medianRow)) { - $even = TRUE; - $offset++; - $medianRow--; - } - - $medianValue = "SELECT {$alias}.total_amount as median - {$sql} {$where} - ORDER BY median LIMIT {$medianRow},{$offset}"; - $medianValDAO = CRM_Core_DAO::executeQuery($medianValue); - while ($medianValDAO->fetch()) { - if ($even) { - $midValue = $midValue + $medianValDAO->median; - } - else { - $median[] = CRM_Utils_Money::format($medianValDAO->median, $currency); - } - } - if ($even) { - $midValue = $midValue / 2; - $median[] = CRM_Utils_Money::format($midValue, $currency); - } - } - return $median; - - default: - return NULL; - } + CRM_Core_Error::deprecatedFunctionWarning('computeStats is now deprecated'); + return []; } /** @@ -4461,7 +4396,7 @@ public static function computeStats($stat, $sql, $alias = NULL) { * @throws \CiviCRM_API3_Exception */ public static function isSingleLineItem($id) { - $lineItemCount = civicrm_api3('LineItem', 'getcount', array('contribution_id' => $id)); + $lineItemCount = civicrm_api3('LineItem', 'getcount', ['contribution_id' => $id]); return ($lineItemCount == 1); } @@ -4480,17 +4415,22 @@ public static function isSingleLineItem($id) { * @param CRM_Core_Transaction $transaction * @param int $recur * @param CRM_Contribute_BAO_Contribution $contribution + * @param bool $isPostPaymentCreate + * Is this being called from the payment.create api. If so the api has taken care of financial entities. + * Note that our goal is that this would only ever be called from payment.create and never handle financials (only + * transitioning related elements). * * @return array + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ - public static function completeOrder(&$input, &$ids, $objects, $transaction, $recur, $contribution) { + public static function completeOrder(&$input, &$ids, $objects, $transaction, $recur, $contribution, $isPostPaymentCreate = FALSE) { $primaryContributionID = isset($contribution->id) ? $contribution->id : $objects['first_contribution']->id; // The previous details are used when calculating line items so keep it before any code that 'does something' if (!empty($contribution->id)) { - $input['prevContribution'] = CRM_Contribute_BAO_Contribution::getValues(array('id' => $contribution->id), - CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); + $input['prevContribution'] = CRM_Contribute_BAO_Contribution::getValues(['id' => $contribution->id]); } - $inputContributionWhiteList = array( + $inputContributionWhiteList = [ 'fee_amount', 'net_amount', 'trxn_id', @@ -4503,13 +4443,12 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re 'contribution_status_id', 'card_type_id', 'pan_truncation', - ); + ]; if (self::isSingleLineItem($primaryContributionID)) { $inputContributionWhiteList[] = 'financial_type_id'; } $participant = CRM_Utils_Array::value('participant', $objects); - $memberships = CRM_Utils_Array::value('membership', $objects); $recurContrib = CRM_Utils_Array::value('contributionRecur', $objects); $recurringContributionID = (empty($recurContrib->id)) ? NULL : $recurContrib->id; $event = CRM_Utils_Array::value('event', $objects); @@ -4526,11 +4465,17 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re $completedContributionStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); - $contributionParams = array_merge(array( + $contributionParams = array_merge([ 'contribution_status_id' => $completedContributionStatusID, 'source' => self::getRecurringContributionDescription($contribution, $event), - ), array_intersect_key($input, array_fill_keys($inputContributionWhiteList, 1) + ], array_intersect_key($input, array_fill_keys($inputContributionWhiteList, 1) )); + + // CRM-20678 Ensure that the currency is correct in subseqent transcations. + if (empty($contributionParams['currency']) && isset($objects['first_contribution']->currency)) { + $contributionParams['currency'] = $objects['first_contribution']->currency; + } + $contributionParams['payment_processor'] = $input['payment_processor'] = $paymentProcessorId; // If paymentProcessor is not set then the payment_instrument_id would not be correct. @@ -4551,11 +4496,7 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re self::repeatTransaction($contribution, $input, $contributionParams, $paymentProcessorId); $contributionParams['financial_type_id'] = $contribution->financial_type_id; - if (is_numeric($memberships)) { - $memberships = array($objects['membership']); - } - - $values = array(); + $values = []; if (isset($input['is_email_receipt'])) { $values['is_email_receipt'] = $input['is_email_receipt']; } @@ -4579,94 +4520,19 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re elseif ($recurContrib && $recurringContributionID) { //CRM-13273 - is_email_receipt setting on recurring contribution should take precedence over contribution page setting // but CRM-16124 if $input['is_email_receipt'] is set then that should not be overridden. + // dev/core#1245 this maybe not the desired effect because the default value for is_email_receipt is set to 0 rather than 1 in + // Instance that had the table added via an upgrade in 4.1 + // see also https://github.com/civicrm/civicrm-svn/commit/7f39befd60bc735408d7866b02b3ac7fff1d4eea#diff-9ad8e290180451a2d6eacbd3d1ca7966R354 + // https://lab.civicrm.org/dev/core/issues/1245 $values['is_email_receipt'] = $recurContrib->is_email_receipt; } - if (!empty($memberships)) { - foreach ($memberships as $membershipTypeIdKey => $membership) { - if ($membership) { - $membershipParams = array( - 'id' => $membership->id, - 'contact_id' => $membership->contact_id, - 'is_test' => $membership->is_test, - 'membership_type_id' => $membership->membership_type_id, - ); - - $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'], - $membershipParams['membership_type_id'], - $membershipParams['is_test'], - $membershipParams['id'] - ); - - // CRM-8141 update the membership type with the value recorded in log when membership created/renewed - // this picks up membership type changes during renewals - // @todo this is almost certainly an obsolete sql call, the pre-change - // membership is accessible via $this->_relatedObjects - $sql = " -SELECT membership_type_id -FROM civicrm_membership_log -WHERE membership_id={$membershipParams['id']} -ORDER BY id DESC -LIMIT 1;"; - $dao = CRM_Core_DAO::executeQuery($sql); - if ($dao->fetch()) { - if (!empty($dao->membership_type_id)) { - $membershipParams['membership_type_id'] = $dao->membership_type_id; - } - } - $dao->free(); - - if (CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', CRM_Utils_Array::value('contribution_status_id', $input)) === 'Pending') { - $membershipParams['num_terms'] = 0; - } - else { - $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType( - $membershipParams['membership_type_id'], - $primaryContributionID - ); - // @todo remove all this stuff in favour of letting the api call further down handle in - // (it is a duplication of what the api does). - $dates = array_fill_keys(array('join_date', 'start_date', 'end_date'), NULL); - if ($currentMembership) { - /* - * Fixed FOR CRM-4433 - * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status - * when Contribution mode is notify and membership is for renewal ) - */ - CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate); - - // @todo - we should pass membership_type_id instead of null here but not - // adding as not sure of testing - $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'], - $changeDate, NULL, $membershipParams['num_terms'] - ); - - $dates['join_date'] = $currentMembership['join_date']; - } - - //get the status for membership. - $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'], - $dates['end_date'], - $dates['join_date'], - 'today', - TRUE, - $membershipParams['membership_type_id'], - $membershipParams - ); - - unset($dates['end_date']); - $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New'); - //we might be renewing membership, - //so make status override false. - $membershipParams['is_override'] = FALSE; - } - //CRM-17723 - reset static $relatedContactIds array() - // @todo move it to Civi Statics. - $var = TRUE; - CRM_Member_BAO_Membership::createRelatedMemberships($var, $var, TRUE); - civicrm_api3('Membership', 'create', $membershipParams); - } - } + if ($contributionParams['contribution_status_id'] === $completedContributionStatusID) { + self::updateMembershipBasedOnCompletionOfContribution( + $contribution, + $primaryContributionID, + $changeDate + ); } } else { @@ -4682,6 +4548,7 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re } $contributionParams['id'] = $contribution->id; + $contributionParams['is_post_payment_create'] = $isPostPaymentCreate; // CRM-19309 - if you update the contribution here with financial_type_id it can/will mess with $lineItem // unsetting it here does NOT cause any other contribution test to fail! @@ -4689,14 +4556,14 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re $contributionResult = civicrm_api3('Contribution', 'create', $contributionParams); // Add new soft credit against current $contribution. - if (CRM_Utils_Array::value('contributionRecur', $objects) && $objects['contributionRecur']->id) { + if (!empty($objects['contributionRecur']) && $objects['contributionRecur']->id) { CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($objects['contributionRecur']->id, $contribution->id); } - $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', array( + $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', [ 'labelColumn' => 'name', 'flip' => 1, - )); + ]); if (isset($input['prevContribution']) && (!$input['prevContribution']->is_pay_later && $input['prevContribution']->contribution_status_id == $contributionStatuses['Pending'])) { $input['payment_processor'] = $paymentProcessorId; } @@ -4706,6 +4573,7 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re $input['participant_id'] = $contribution->_relatedObjects['participant']->id; } elseif (!empty($contribution->_relatedObjects['membership'])) { + // @todo - use getRelatedMemberships instead $input['contribution_mode'] = 'membership'; $contribution->contribution_status_id = $contributionParams['contribution_status_id']; $contribution->trxn_id = CRM_Utils_Array::value('trxn_id', $input); @@ -4727,10 +4595,6 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re $contribution->contact_id = $ids['related_contact']; } CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID); - // event - } - else { - CRM_Activity_BAO_Activity::addActivity($participant); } // CRM-9132 legacy behaviour was that receipts were sent out in all instances. Still sending @@ -4738,10 +4602,10 @@ public static function completeOrder(&$input, &$ids, $objects, $transaction, $re if (!array_key_exists('is_email_receipt', $values) || $values['is_email_receipt'] == 1 ) { - civicrm_api3('Contribution', 'sendconfirmation', array( + civicrm_api3('Contribution', 'sendconfirmation', [ 'id' => $contribution->id, 'payment_processor_id' => $paymentProcessorId, - )); + ]); CRM_Core_Error::debug_log_message("Receipt sent"); } @@ -4785,30 +4649,44 @@ public static function sendMail(&$input, &$ids, $contributionID, &$values, if (!$returnMessageText) { list($values['receipt_from_name'], $values['receipt_from_email']) = self::generateFromEmailAndName($input, $contribution); } + $values['contribution_status'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution->contribution_status_id); $return = $contribution->composeMessageArray($input, $ids, $values, $returnMessageText); - // Contribution ID should really always be set. But ? - if (!$returnMessageText && (!isset($input['receipt_update']) || $input['receipt_update']) && empty($contribution->receipt_date)) { - civicrm_api3('Contribution', 'create', array('receipt_date' => 'now', 'id' => $contribution->id)); + if ((!isset($input['receipt_update']) || $input['receipt_update']) && empty($contribution->receipt_date)) { + civicrm_api3('Contribution', 'create', [ + 'receipt_date' => 'now', + 'id' => $contribution->id, + ]); } return $return; } /** * Generate From email and from name in an array values + * + * @param array $input + * @param \CRM_Contribute_BAO_Contribution $contribution + * + * @return array */ public static function generateFromEmailAndName($input, $contribution) { - // Use input valuse if supplied. + // Use input value if supplied. if (!empty($input['receipt_from_email'])) { - return array(CRM_Utils_array::value('receipt_from_name', $input, ''), $input['receipt_from_email']); + return [ + CRM_Utils_Array::value('receipt_from_name', $input, ''), + $input['receipt_from_email'], + ]; } // if we are still empty see if we can use anything from a contribution page. - $pageValues = array(); + $pageValues = []; if (!empty($contribution->contribution_page_id)) { - $pageValues = civicrm_api3('ContributionPage', 'getsingle', array('id' => $contribution->contribution_page_id)); + $pageValues = civicrm_api3('ContributionPage', 'getsingle', ['id' => $contribution->contribution_page_id]); } // if we are still empty see if we can use anything from a contribution page. if (!empty($pageValues['receipt_from_email'])) { - return array($pageValues['receipt_from_name'], $pageValues['receipt_from_email']); + return [ + CRM_Utils_Array::value('receipt_from_name', $pageValues), + $pageValues['receipt_from_email'], + ]; } // If we are still empty fall back to the domain or logged in user information. return CRM_Core_BAO_Domain::getDefaultReceiptFrom(); @@ -4823,16 +4701,16 @@ public static function generateFromEmailAndName($input, $contribution) { public static function createCreditNoteId() { $prefixValue = Civi::settings()->get('contribution_invoice_settings'); - $creditNoteNum = CRM_Core_DAO::singleValueQuery("SELECT count(creditnote_id) as creditnote_number FROM civicrm_contribution"); + $creditNoteNum = CRM_Core_DAO::singleValueQuery("SELECT count(creditnote_id) as creditnote_number FROM civicrm_contribution WHERE creditnote_id IS NOT NULL"); $creditNoteId = NULL; do { $creditNoteNum++; $creditNoteId = CRM_Utils_Array::value('credit_notes_prefix', $prefixValue) . "" . $creditNoteNum; - $result = civicrm_api3('Contribution', 'getcount', array( + $result = civicrm_api3('Contribution', 'getcount', [ 'sequential' => 1, 'creditnote_id' => $creditNoteId, - )); + ]); } while ($result > 0); return $creditNoteId; @@ -4841,6 +4719,13 @@ public static function createCreditNoteId() { /** * Load related memberships. * + * @param array $ids + * + * @return array $ids + * + * @throws Exception + * @deprecated + * * Note that in theory it should be possible to retrieve these from the line_item table * with the membership_payment table being deprecated. Attempting to do this here causes tests to fail * as it seems the api is not correctly linking the line items when the contribution is created in the flow @@ -4849,17 +4734,14 @@ public static function createCreditNoteId() { * * I don't know if it never worked or broke as a result of https://issues.civicrm.org/jira/browse/CRM-14918. * - * @param array $ids - * - * @throws Exception */ - public function loadRelatedMembershipObjects(&$ids) { + public function loadRelatedMembershipObjects($ids = []) { $query = " SELECT membership_id FROM civicrm_membership_payment WHERE contribution_id = %1 "; - $params = array(1 => array($this->id, 'Integer')); - $ids['membership'] = (array) CRM_Utils_Array::value('membership', $ids, array()); + $params = [1 => [$this->id, 'Integer']]; + $ids['membership'] = (array) CRM_Utils_Array::value('membership', $ids, []); $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { @@ -4880,10 +4762,10 @@ public function loadRelatedMembershipObjects(&$ids) { $membership->start_date = CRM_Utils_Date::isoToMysql($membership->start_date); $membership->end_date = CRM_Utils_Date::isoToMysql($membership->end_date); $this->_relatedObjects['membership'][$membership->membership_type_id] = $membership; - $membership->free(); } } } + return $ids; } /** @@ -4893,47 +4775,28 @@ public function loadRelatedMembershipObjects(&$ids) { * * @param array $params * - * @return object + * @return CRM_Financial_DAO_FinancialTrxn */ public static function recordPartialPayment($contribution, $params) { - $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - $pendingStatus = array( - array_search('Pending', $contributionStatuses), - array_search('In Progress', $contributionStatuses), - ); - $statusId = array_search('Completed', $contributionStatuses); - if (in_array(CRM_Utils_Array::value('contribution_status_id', $contribution), $pendingStatus)) { - $balanceTrxnParams['to_financial_account_id'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contribution['financial_type_id'], 'Accounts Receivable Account is'); - } - elseif (!empty($params['payment_processor'])) { - $balanceTrxnParams['to_financial_account_id'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contribution['payment_processor'], NULL, 'civicrm_payment_processor'); - } - elseif (!empty($params['payment_instrument_id'])) { - $balanceTrxnParams['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contribution['payment_instrument_id']); - } - else { - $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' ")); - $queryParams = array(1 => array($relationTypeId, 'Integer')); - $balanceTrxnParams['to_financial_account_id'] = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams); - } - $fromFinancialAccountId = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contribution['financial_type_id'], 'Accounts Receivable Account is'); - $balanceTrxnParams['from_financial_account_id'] = $fromFinancialAccountId; + CRM_Core_Error::deprecatedFunctionWarning('use payment create api'); + $balanceTrxnParams['to_financial_account_id'] = self::getToFinancialAccount($contribution, $params); + $balanceTrxnParams['from_financial_account_id'] = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($contribution['financial_type_id'], 'Accounts Receivable Account is'); $balanceTrxnParams['total_amount'] = $params['total_amount']; $balanceTrxnParams['contribution_id'] = $params['contribution_id']; - $balanceTrxnParams['trxn_date'] = !empty($params['contribution_receive_date']) ? $params['contribution_receive_date'] : date('YmdHis'); + $balanceTrxnParams['trxn_date'] = CRM_Utils_Array::value('trxn_date', $params, CRM_Utils_Array::value('contribution_receive_date', $params, date('YmdHis'))); $balanceTrxnParams['fee_amount'] = CRM_Utils_Array::value('fee_amount', $params); $balanceTrxnParams['net_amount'] = CRM_Utils_Array::value('total_amount', $params); $balanceTrxnParams['currency'] = $contribution['currency']; $balanceTrxnParams['trxn_id'] = CRM_Utils_Array::value('contribution_trxn_id', $params, NULL); - $balanceTrxnParams['status_id'] = $statusId; + $balanceTrxnParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'status_id', 'Completed'); $balanceTrxnParams['payment_instrument_id'] = CRM_Utils_Array::value('payment_instrument_id', $params, $contribution['payment_instrument_id']); $balanceTrxnParams['check_number'] = CRM_Utils_Array::value('check_number', $params); - if ($fromFinancialAccountId != NULL && - ($statusId == array_search('Completed', $contributionStatuses) || $statusId == array_search('Partially paid', $contributionStatuses)) - ) { - $balanceTrxnParams['is_payment'] = 1; - } + $balanceTrxnParams['is_payment'] = 1; + if (!empty($params['payment_processor'])) { + // I can't find evidence this is passed in - I was gonna just remove it but decided to deprecate as I see self::getToFinancialAccount + // also anticipates it. + CRM_Core_Error::deprecatedFunctionWarning('passing payment_processor is deprecated - use payment_processor_id'); $balanceTrxnParams['payment_processor_id'] = $params['payment_processor']; } return CRM_Core_BAO_FinancialTrxn::create($balanceTrxnParams); @@ -4945,7 +4808,7 @@ public static function recordPartialPayment($contribution, $params) { * @param CRM_Contribute_BAO_Contribution $contribution * @param CRM_Event_DAO_Event|null $event * - * @return array + * @return string * @throws \CiviCRM_API3_Exception */ protected static function getRecurringContributionDescription($contribution, $event) { @@ -4953,10 +4816,10 @@ protected static function getRecurringContributionDescription($contribution, $ev return $contribution->source; } elseif (!empty($contribution->contribution_page_id) && is_numeric($contribution->contribution_page_id)) { - $contributionPageTitle = civicrm_api3('ContributionPage', 'getvalue', array( + $contributionPageTitle = civicrm_api3('ContributionPage', 'getvalue', [ 'id' => $contribution->contribution_page_id, 'return' => 'title', - )); + ]); return ts('Online Contribution') . ': ' . $contributionPageTitle; } elseif ($event) { @@ -4973,7 +4836,7 @@ protected static function getRecurringContributionDescription($contribution, $ev * for Partially Paid status * * @param array $contributions - * @param array $contributionStatusId + * @param string $contributionStatusId * */ public static function addPayments($contributions, $contributionStatusId = NULL) { @@ -4982,54 +4845,60 @@ public static function addPayments($contributions, $contributionStatusId = NULL) FROM civicrm_financial_trxn ft INNER JOIN civicrm_entity_financial_trxn eft ON eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution' WHERE eft.entity_id = %1 AND ft.is_payment = 1 ORDER BY ft.id DESC LIMIT 1"; - $contributionStatus = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', array( + $contributionStatus = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', [ 'labelColumn' => 'name', - )); - foreach ($contributions as $k => $contribution) { + ]); + foreach ($contributions as $contribution) { if (!($contributionStatus[$contribution->contribution_status_id] == 'Partially paid' || CRM_Utils_Array::value($contributionStatusId, $contributionStatus) == 'Partially paid') ) { continue; } - $ftDao = CRM_Core_DAO::executeQuery($ftSql, array(1 => array($contribution->id, 'Integer'))); + $ftDao = CRM_Core_DAO::executeQuery($ftSql, [ + 1 => [ + $contribution->id, + 'Integer', + ], + ]); $ftDao->fetch(); // store financial item Proportionaly. - $trxnParams = array( + $trxnParams = [ 'total_amount' => $ftDao->total_amount, 'contribution_id' => $contribution->id, - ); + ]; self::assignProportionalLineItems($trxnParams, $ftDao->id, $contribution->total_amount); } } /** - * Function use to store line item proportionaly in - * in entity financial trxn table + * Function use to store line item proportionally in in entity financial trxn table * * @param array $trxnParams * - * @param Integer $trxnId + * @param int $trxnId * * @param float $contributionTotalAmount * + * @throws \CiviCRM_API3_Exception */ public static function assignProportionalLineItems($trxnParams, $trxnId, $contributionTotalAmount) { $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($trxnParams['contribution_id']); if (!empty($lineItems)) { // get financial item list($ftIds, $taxItems) = self::getLastFinancialItemIds($trxnParams['contribution_id']); - $entityParams = array( + $entityParams = [ 'contribution_total_amount' => $contributionTotalAmount, 'trxn_total_amount' => $trxnParams['total_amount'], 'trxn_id' => $trxnId, - ); + ]; self::createProportionalFinancialEntries($entityParams, $lineItems, $ftIds, $taxItems); } } /** - * Function to check line items. + * Checks if line items total amounts + * match the contribution total amount. * * @param array $params * array of order params. @@ -5039,19 +4908,29 @@ public static function assignProportionalLineItems($trxnParams, $trxnId, $contri public static function checkLineItems(&$params) { $totalAmount = CRM_Utils_Array::value('total_amount', $params); $lineItemAmount = 0; + foreach ($params['line_items'] as &$lineItems) { foreach ($lineItems['line_item'] as &$item) { if (empty($item['financial_type_id'])) { $item['financial_type_id'] = $params['financial_type_id']; } - $lineItemAmount += $item['line_total']; + $lineItemAmount += $item['line_total'] + CRM_Utils_Array::value('tax_amount', $item, 0.00); } } + if (!isset($totalAmount)) { $params['total_amount'] = $lineItemAmount; } - elseif ($totalAmount != $lineItemAmount) { - throw new API_Exception("Line item total doesn't match with total amount."); + else { + $currency = CRM_Utils_Array::value('currency', $params, ''); + + if (empty($currency)) { + $currency = CRM_Core_Config::singleton()->defaultCurrency; + } + + if (!CRM_Utils_Money::equals($totalAmount, $lineItemAmount, $currency)) { + throw new CRM_Contribute_Exception_CheckLineItemsException(); + } } } @@ -5068,11 +4947,13 @@ public static function getFinancialAccountForStatusChangeTrxn($params, $default) if (!empty($params['financial_account_id'])) { return $params['financial_account_id']; } + $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($params['contribution_status_id'], 'name'); - $preferredAccountsRelationships = array( + $preferredAccountsRelationships = [ 'Refunded' => 'Credit/Contra Revenue Account is', 'Chargeback' => 'Chargeback Account is', - ); + ]; + if (in_array($contributionStatus, array_keys($preferredAccountsRelationships))) { $financialTypeID = !empty($params['financial_type_id']) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id; return CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship( @@ -5080,6 +4961,7 @@ public static function getFinancialAccountForStatusChangeTrxn($params, $default) $preferredAccountsRelationships[$contributionStatus] ); } + return $default; } @@ -5099,12 +4981,12 @@ public static function getFinancialAccountForStatusChangeTrxn($params, $default) * @return array */ protected function addContributionPageValuesToValuesHeavyHandedly(&$values) { - $contributionPageValues = array(); + $contributionPageValues = []; CRM_Contribute_BAO_ContributionPage::setValues( $this->contribution_page_id, $contributionPageValues ); - $valuesToCopy = array( + $valuesToCopy = [ // These are the values that I believe to be useful. 'id', 'title', @@ -5155,10 +5037,15 @@ protected function addContributionPageValuesToValuesHeavyHandedly(&$values) { 'thankyou_text', 'thankyou_title', - ); + ]; foreach ($valuesToCopy as $valueToCopy) { if (isset($contributionPageValues[$valueToCopy])) { - $values[$valueToCopy] = $contributionPageValues[$valueToCopy]; + if ($valueToCopy === 'title') { + $values[$valueToCopy] = CRM_Contribute_BAO_Contribution_Utils::getContributionPageTitle($this->contribution_page_id); + } + else { + $values[$valueToCopy] = $contributionPageValues[$valueToCopy]; + } } } return $values; @@ -5175,12 +5062,17 @@ protected function addContributionPageValuesToValuesHeavyHandedly(&$values) { * * * @param string $name + * @param bool $checkInvoicing * @return string * */ - public static function checkContributeSettings($name = NULL) { + public static function checkContributeSettings($name = NULL, $checkInvoicing = FALSE) { $contributeSettings = Civi::settings()->get('contribution_invoice_settings'); + if ($checkInvoicing && empty($contributeSettings['invoicing'])) { + return NULL; + } + if ($name) { return CRM_Utils_Array::value($name, $contributeSettings); } @@ -5204,12 +5096,12 @@ public static function transitionComponentWithReturnMessage($contributionId, $st return $statusMsg; } - $params = array( + $params = [ 'contribution_id' => $contributionId, 'contribution_status_id' => $statusId, 'previous_contribution_status_id' => $previousStatusId, 'receive_date' => $receiveDate, - ); + ]; $updateResult = CRM_Contribute_BAO_Contribution::transitionComponents($params); @@ -5236,23 +5128,19 @@ public static function transitionComponentWithReturnMessage($contributionId, $st $updatedStatusName = CRM_Utils_Array::value($updatedStatusId, CRM_Member_PseudoConstant::membershipStatus() ); - if ($updatedStatusName == 'Cancelled') { - $statusMsg .= "
    " . ts("Membership for %1 has been Cancelled.", array(1 => $userDisplayName)); - } - elseif ($updatedStatusName == 'Expired') { - $statusMsg .= "
    " . ts("Membership for %1 has been Expired.", array(1 => $userDisplayName)); - } - else { - $endDate = CRM_Utils_Array::value('membership_end_date', $updateResult); - if ($endDate) { - $statusMsg .= "
    " . ts("Membership for %1 has been updated. The membership End Date is %2.", - array( - 1 => $userDisplayName, - 2 => $endDate, - ) - ); - } + + $statusNameMsgPart = 'updated'; + switch ($updatedStatusName) { + case 'Cancelled': + case 'Expired': + $statusNameMsgPart = $updatedStatusName; + break; } + + $statusMsg .= "
    " . ts("Membership for %1 has been %2.", [ + 1 => $userDisplayName, + 2 => $statusNameMsgPart, + ]); } if ($componentName == 'CiviEvent') { @@ -5260,10 +5148,10 @@ public static function transitionComponentWithReturnMessage($contributionId, $st CRM_Event_PseudoConstant::participantStatus() ); if ($updatedStatusName == 'Cancelled') { - $statusMsg .= "
    " . ts("Event Registration for %1 has been Cancelled.", array(1 => $userDisplayName)); + $statusMsg .= "
    " . ts("Event Registration for %1 has been Cancelled.", [1 => $userDisplayName]); } elseif ($updatedStatusName == 'Registered') { - $statusMsg .= "
    " . ts("Event Registration for %1 has been updated.", array(1 => $userDisplayName)); + $statusMsg .= "
    " . ts("Event Registration for %1 has been updated.", [1 => $userDisplayName]); } } @@ -5272,13 +5160,13 @@ public static function transitionComponentWithReturnMessage($contributionId, $st CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name') ); if ($updatedStatusName == 'Cancelled') { - $statusMsg .= "
    " . ts("Pledge Payment for %1 has been Cancelled.", array(1 => $userDisplayName)); + $statusMsg .= "
    " . ts("Pledge Payment for %1 has been Cancelled.", [1 => $userDisplayName]); } elseif ($updatedStatusName == 'Failed') { - $statusMsg .= "
    " . ts("Pledge Payment for %1 has been Failed.", array(1 => $userDisplayName)); + $statusMsg .= "
    " . ts("Pledge Payment for %1 has been Failed.", [1 => $userDisplayName]); } elseif ($updatedStatusName == 'Completed') { - $statusMsg .= "
    " . ts("Pledge Payment for %1 has been updated.", array(1 => $userDisplayName)); + $statusMsg .= "
    " . ts("Pledge Payment for %1 has been updated.", [1 => $userDisplayName]); } } } @@ -5291,10 +5179,10 @@ public static function transitionComponentWithReturnMessage($contributionId, $st * * @param int $contributionID * - * @return array + * @return \CRM_Contribute_BAO_Contribution|null */ private static function getOriginalContribution($contributionID) { - return self::getValues(array('id' => $contributionID), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); + return self::getValues(['id' => $contributionID]); } /** @@ -5308,8 +5196,6 @@ private static function getOriginalContribution($contributionID) { * for historical reasons. Going forwards we can hope to add tests & improve readibility * of that function * - * @todo move recordFinancialAccounts & helper functions to their own class? - * * @param array $params * Params as passed to contribution.create * @@ -5319,32 +5205,42 @@ private static function getOriginalContribution($contributionID) { * Line items. * @param bool $isARefund * Is this a refund / negative transaction. + * @param int $previousLineItemTotal * * @return float + * @todo move recordFinancialAccounts & helper functions to their own class? + * */ - protected static function getFinancialItemAmountFromParams($params, $context, $lineItemDetails, $isARefund) { - if ($context == 'changedAmount' || $context == 'changeFinancialType') { - return $params['total_amount'] - $params['prevContribution']->total_amount; + protected static function getFinancialItemAmountFromParams($params, $context, $lineItemDetails, $isARefund, $previousLineItemTotal) { + if ($context == 'changedAmount') { + $lineTotal = $lineItemDetails['line_total']; + if ($lineTotal != $previousLineItemTotal) { + $lineTotal -= $previousLineItemTotal; + } + return $lineTotal; + } + elseif ($context == 'changeFinancialType') { + return -$lineItemDetails['line_total']; } elseif ($context == 'changedStatus') { $cancelledTaxAmount = 0; if ($isARefund) { - $cancelledTaxAmount = CRM_Utils_Array::value('tax_amount', $params, '0.00'); + $cancelledTaxAmount = CRM_Utils_Array::value('tax_amount', $lineItemDetails, '0.00'); } - return self::getMultiplier($params['contribution']->contribution_status_id, $context) * ($params['trxnParams']['total_amount'] + $cancelledTaxAmount); + return self::getMultiplier($params['contribution']->contribution_status_id, $context) * ((float) $lineItemDetails['line_total'] + (float) $cancelledTaxAmount); } elseif ($context === NULL) { // erm, yes because? but, hey, it's tested. - return $params['total_amount']; + return $lineItemDetails['line_total']; } elseif (empty($lineItemDetails['line_total'])) { // follow legacy code path Civi::log() - ->warning('Deprecated bit of code, please log a ticket explaining how you got here!', array('civi.tag' => 'deprecated')); + ->warning('Deprecated bit of code, please log a ticket explaining how you got here!', ['civi.tag' => 'deprecated']); return $params['total_amount']; } else { - return self::getMultiplier($params['contribution']->contribution_status_id, $context) * $lineItemDetails['line_total']; + return self::getMultiplier($params['contribution']->contribution_status_id, $context) * ((float) $lineItemDetails['line_total']); } } @@ -5369,6 +5265,285 @@ protected static function getMultiplier($contribution_status_id, $context) { return 1; } + /** + * Does this transaction reflect a payment instrument change. + * + * @param array $params + * @param array $pendingStatuses + * + * @return bool + */ + protected static function isPaymentInstrumentChange(&$params, $pendingStatuses) { + $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution']->contribution_status_id); + + if (array_key_exists('payment_instrument_id', $params)) { + if (CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id) && + !CRM_Utils_System::isNull($params['payment_instrument_id']) + ) { + //check if status is changed from Pending to Completed + // do not update payment instrument changes for Pending to Completed + if (!($contributionStatus == 'Completed' && + in_array($params['prevContribution']->contribution_status_id, $pendingStatuses)) + ) { + return TRUE; + } + } + elseif ((!CRM_Utils_System::isNull($params['payment_instrument_id']) && + !CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id)) && + $params['payment_instrument_id'] != $params['prevContribution']->payment_instrument_id + ) { + return TRUE; + } + elseif (!CRM_Utils_System::isNull($params['contribution']->check_number) && + $params['contribution']->check_number != $params['prevContribution']->check_number + ) { + // another special case when check number is changed, create new financial records + // create financial trxn with negative amount + return TRUE; + } + } + return FALSE; + } + + /** + * Update the memberships associated with a contribution if it has been completed. + * + * Note that the way in which $memberships are loaded as objects is pretty messy & I think we could just + * load them in this function. Code clean up would compensate for any minor performance implication. + * + * @param \CRM_Contribute_BAO_Contribution $contribution + * @param int $primaryContributionID + * @param string $changeDate + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + public static function updateMembershipBasedOnCompletionOfContribution($contribution, $primaryContributionID, $changeDate) { + $memberships = self::getRelatedMemberships($contribution->id); + foreach ($memberships as $membership) { + $membershipParams = [ + 'id' => $membership['id'], + 'contact_id' => $membership['contact_id'], + 'is_test' => $membership['is_test'], + 'membership_type_id' => $membership['membership_type_id'], + 'membership_activity_status' => 'Completed', + ]; + + $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'], + $membershipParams['membership_type_id'], + $membershipParams['is_test'], + $membershipParams['id'] + ); + + // CRM-8141 update the membership type with the value recorded in log when membership created/renewed + // this picks up membership type changes during renewals + // @todo this is almost certainly an obsolete sql call, the pre-change + // membership is accessible via $this->_relatedObjects + $sql = " +SELECT membership_type_id +FROM civicrm_membership_log +WHERE membership_id={$membershipParams['id']} +ORDER BY id DESC +LIMIT 1;"; + $dao = CRM_Core_DAO::executeQuery($sql); + if ($dao->fetch()) { + if (!empty($dao->membership_type_id)) { + $membershipParams['membership_type_id'] = $dao->membership_type_id; + } + } + if (empty($membership['end_date']) || (int) $membership['status_id'] !== CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', 'Pending')) { + // Passing num_terms to the api triggers date calculations, but for pending memberships these may be already calculated. + // sigh - they should be consistent but removing the end date check causes test failures & maybe UI too? + // The api assumes num_terms is a special sauce for 'is_renewal' so we need to not pass it when updating a pending to completed. + // @todo once apiv4 ships with core switch to that & find sanity. + $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType( + $membershipParams['membership_type_id'], + $primaryContributionID + ); + } + // @todo remove all this stuff in favour of letting the api call further down handle in + // (it is a duplication of what the api does). + $dates = array_fill_keys([ + 'join_date', + 'start_date', + 'end_date', + ], NULL); + if ($currentMembership) { + /* + * Fixed FOR CRM-4433 + * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status + * when Contribution mode is notify and membership is for renewal ) + */ + CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate); + + // @todo - we should pass membership_type_id instead of null here but not + // adding as not sure of testing + $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'], + $changeDate, NULL, $membershipParams['num_terms'] + ); + $dates['join_date'] = $currentMembership['join_date']; + } + + //get the status for membership. + $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'], + $dates['end_date'], + $dates['join_date'], + 'today', + TRUE, + $membershipParams['membership_type_id'], + $membershipParams + ); + + unset($dates['end_date']); + $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New'); + //we might be renewing membership, + //so make status override false. + $membershipParams['is_override'] = FALSE; + $membershipParams['status_override_end_date'] = 'null'; + + //CRM-17723 - reset static $relatedContactIds array() + // @todo move it to Civi Statics. + $var = TRUE; + CRM_Member_BAO_Membership::createRelatedMemberships($var, $var, TRUE); + civicrm_api3('Membership', 'create', $membershipParams); + } + } + + /** + * Get payment links as they relate to a contribution. + * + * If a payment can be made then include a payment link & if a refund is appropriate + * then a refund link. + * + * @param int $id + * @param float $balance + * @param string $contributionStatus + * + * @return array + * $actionLinks Links array containing: + * -url + * -title + */ + protected static function getContributionPaymentLinks($id, $balance, $contributionStatus) { + if ($contributionStatus === 'Failed' || !CRM_Core_Permission::check('edit contributions')) { + // In general the balance is the best way to determine if a payment can be added or not, + // but not for Failed contributions, where we don't accept additional payments at the moment. + // (in some cases the contribution is 'Pending' and only the payment is failed. In those we + // do accept more payments agains them. + return []; + } + $actionLinks = []; + if ((int) $balance > 0) { + if (CRM_Core_Config::isEnabledBackOfficeCreditCardPayments()) { + $actionLinks[] = [ + 'url' => CRM_Utils_System::url('civicrm/payment', [ + 'action' => 'add', + 'reset' => 1, + 'id' => $id, + 'mode' => 'live', + ]), + 'title' => ts('Submit Credit Card payment'), + ]; + } + $actionLinks[] = [ + 'url' => CRM_Utils_System::url('civicrm/payment', [ + 'action' => 'add', + 'reset' => 1, + 'id' => $id, + ]), + 'title' => ts('Record Payment'), + ]; + } + elseif ((int) $balance < 0) { + $actionLinks[] = [ + 'url' => CRM_Utils_System::url('civicrm/payment', [ + 'action' => 'add', + 'reset' => 1, + 'id' => $id, + ]), + 'title' => ts('Record Refund'), + ]; + } + return $actionLinks; + } + + /** + * Get a query to determine the amount donated by the contact/s in the current financial year. + * + * @param array $contactIDs + * + * @return string + */ + public static function getAnnualQuery($contactIDs) { + $contactIDs = implode(',', $contactIDs); + $config = CRM_Core_Config::singleton(); + $currentMonth = date('m'); + $currentDay = date('d'); + if ( + (int) $config->fiscalYearStart['M'] > $currentMonth || + ( + (int) $config->fiscalYearStart['M'] == $currentMonth && + (int) $config->fiscalYearStart['d'] > $currentDay + ) + ) { + $year = date('Y') - 1; + } + else { + $year = date('Y'); + } + $nextYear = $year + 1; + + if ($config->fiscalYearStart) { + $newFiscalYearStart = $config->fiscalYearStart; + if ($newFiscalYearStart['M'] < 10) { + // This is just a clumsy way of adding padding. + // @todo next round look for a nicer way. + $newFiscalYearStart['M'] = '0' . $newFiscalYearStart['M']; + } + if ($newFiscalYearStart['d'] < 10) { + // This is just a clumsy way of adding padding. + // @todo next round look for a nicer way. + $newFiscalYearStart['d'] = '0' . $newFiscalYearStart['d']; + } + $config->fiscalYearStart = $newFiscalYearStart; + $monthDay = $config->fiscalYearStart['M'] . $config->fiscalYearStart['d']; + } + else { + // First of January. + $monthDay = '0101'; + } + $startDate = "$year$monthDay"; + $endDate = "$nextYear$monthDay"; + + $whereClauses = [ + 'contact_id' => 'IN (' . $contactIDs . ')', + 'is_test' => ' = 0', + 'receive_date' => ['>=' . $startDate, '< ' . $endDate], + ]; + $havingClause = 'contribution_status_id = ' . (int) CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); + CRM_Financial_BAO_FinancialType::addACLClausesToWhereClauses($whereClauses); + + $clauses = []; + foreach ($whereClauses as $key => $clause) { + $clauses[] = 'b.' . $key . " " . implode(' AND b.' . $key, (array) $clause); + } + $whereClauseString = implode(' AND ', $clauses); + + // See https://github.com/civicrm/civicrm-core/pull/13512 for discussion of how + // this group by + having on contribution_status_id improves performance + $query = " + SELECT COUNT(*) as count, + SUM(total_amount) as amount, + AVG(total_amount) as average, + currency + FROM civicrm_contribution b + WHERE " . $whereClauseString . " + GROUP BY currency, contribution_status_id + HAVING $havingClause + "; + return $query; + } + /** * Assign Test Value. * @@ -5427,13 +5602,13 @@ public static function allowUpdateRevenueRecognitionDate($contributionId) { * * @param array $trxnParams * Financial trxn params - * @param string $contributionParams + * @param array $contributionParams * Contribution Params * - * @return string + * @return null */ public static function recordAlwaysAccountsReceivable(&$trxnParams, $contributionParams) { - if (!self::checkContributeSettings('always_post_to_accounts_receivable')) { + if (!Civi::settings()->get('always_post_to_accounts_receivable')) { return NULL; } $statusId = $contributionParams['contribution']->contribution_status_id; @@ -5442,9 +5617,9 @@ public static function recordAlwaysAccountsReceivable(&$trxnParams, $contributio $previousContributionStatus = empty($contributionParams['prevContribution']) ? NULL : $contributionStatuses[$contributionParams['prevContribution']->contribution_status_id]; // Return if contribution status is not completed. if (!($contributionStatus == 'Completed' && (empty($previousContributionStatus) - || (!empty($previousContributionStatus) && $previousContributionStatus == 'Pending' - && $contributionParams['prevContribution']->is_pay_later == 0 - ))) + || (!empty($previousContributionStatus) && $previousContributionStatus == 'Pending' + && $contributionParams['prevContribution']->is_pay_later == 0 + ))) ) { return NULL; } @@ -5503,9 +5678,9 @@ public static function getSalesTaxFinancialAccounts() { INNER JOIN civicrm_financial_account cfa ON ce.financial_account_id = cfa.id WHERE `entity_table` = 'civicrm_financial_type' AND cfa.is_tax = 1 AND ce.account_relationship = %1 GROUP BY cfa.id"; $accountRel = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Sales Tax Account is' ")); - $queryParams = array(1 => array($accountRel, 'Integer')); + $queryParams = [1 => [$accountRel, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $queryParams); - $financialAccount = array(); + $financialAccount = []; while ($dao->fetch()) { $financialAccount[$dao->id] = $dao->id; } @@ -5519,9 +5694,13 @@ public static function getSalesTaxFinancialAccounts() { * * @param array $eftParams * + * @throws \CiviCRM_API3_Exception */ public static function createProportionalEntry($entityParams, $eftParams) { - $paid = $entityParams['line_item_amount'] * ($entityParams['trxn_total_amount'] / $entityParams['contribution_total_amount']); + $paid = 0; + if ($entityParams['contribution_total_amount'] != 0) { + $paid = $entityParams['line_item_amount'] * ($entityParams['trxn_total_amount'] / $entityParams['contribution_total_amount']); + } // Record Entity Financial Trxn; CRM-20145 $eftParams['amount'] = CRM_Contribute_BAO_Contribution_Utils::formatAmount($paid); civicrm_api3('EntityFinancialTrxn', 'create', $eftParams); @@ -5539,22 +5718,27 @@ public static function getLastFinancialItemIds($contributionId) { FROM civicrm_financial_item fi INNER JOIN civicrm_line_item li ON li.id = fi.entity_id and fi.entity_table = 'civicrm_line_item' WHERE li.contribution_id = %1"; - $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($contributionId, 'Integer'))); - $ftIds = $taxItems = array(); + $dao = CRM_Core_DAO::executeQuery($sql, [ + 1 => [ + $contributionId, + 'Integer', + ], + ]); + $ftIds = $taxItems = []; $salesTaxFinancialAccount = self::getSalesTaxFinancialAccounts(); while ($dao->fetch()) { /* if sales tax item*/ if (in_array($dao->financial_account_id, $salesTaxFinancialAccount)) { - $taxItems[$dao->price_field_value_id] = array( + $taxItems[$dao->price_field_value_id] = [ 'financial_item_id' => $dao->id, 'amount' => $dao->tax_amount, - ); + ]; } else { $ftIds[$dao->price_field_value_id] = $dao->id; } } - return array($ftIds, $taxItems); + return [$ftIds, $taxItems]; } /** @@ -5568,12 +5752,13 @@ public static function getLastFinancialItemIds($contributionId) { * * @param array $taxItems * + * @throws \CiviCRM_API3_Exception */ public static function createProportionalFinancialEntries($entityParams, $lineItems, $ftIds, $taxItems) { - $eftParams = array( + $eftParams = [ 'entity_table' => 'civicrm_financial_item', 'financial_trxn_id' => $entityParams['trxn_id'], - ); + ]; foreach ($lineItems as $key => $value) { if ($value['qty'] == 0) { continue; @@ -5597,13 +5782,13 @@ public static function createProportionalFinancialEntries($entityParams, $lineIt * @throws \CRM_Core_Exception */ protected function loadRelatedEntitiesByID($ids) { - $entities = array( + $entities = [ 'contact' => 'CRM_Contact_BAO_Contact', 'contributionRecur' => 'CRM_Contribute_BAO_ContributionRecur', 'contributionType' => 'CRM_Financial_BAO_FinancialType', 'financialType' => 'CRM_Financial_BAO_FinancialType', 'contributionPage' => 'CRM_Contribute_BAO_ContributionPage', - ); + ]; foreach ($entities as $entity => $bao) { if (!empty($ids[$entity])) { $this->_relatedObjects[$entity] = new $bao(); @@ -5650,6 +5835,7 @@ protected function isEmailReceipt($input) { * @param bool $escapeSmarty * * @return array + * @throws \CiviCRM_API3_Exception */ public static function replaceContributionTokens( $contributionIds, @@ -5661,11 +5847,11 @@ public static function replaceContributionTokens( $escapeSmarty ) { if (empty($contributionIds)) { - return array(); + return []; } - $contributionDetails = array(); + $contributionDetails = []; foreach ($contributionIds as $id) { - $result = civicrm_api3('Contribution', 'get', array('id' => $id)); + $result = self::getContributionTokenValues($id, $messageToken); $contributionDetails[$result['values'][$result['id']]['contact_id']]['subject'] = CRM_Utils_Token::replaceContributionTokens($subject, $result, FALSE, $subjectToken, FALSE, $escapeSmarty); $contributionDetails[$result['values'][$result['id']]['contact_id']]['text'] = CRM_Utils_Token::replaceContributionTokens($text, $result, FALSE, $messageToken, FALSE, $escapeSmarty); $contributionDetails[$result['values'][$result['id']]['contact_id']]['html'] = CRM_Utils_Token::replaceContributionTokens($html, $result, FALSE, $messageToken, FALSE, $escapeSmarty); @@ -5673,4 +5859,143 @@ public static function replaceContributionTokens( return $contributionDetails; } + /** + * Get the contribution fields for $id and display labels where + * appropriate (if the token is present). + * + * @param int $id + * @param array $messageToken + * @return array + */ + public static function getContributionTokenValues($id, $messageToken) { + if (empty($id)) { + return []; + } + $result = civicrm_api3('Contribution', 'get', ['id' => $id]); + // lab.c.o mail#46 - show labels, not values, for custom fields with option values. + if (!empty($messageToken)) { + foreach ($result['values'][$id] as $fieldName => $fieldValue) { + if (strpos($fieldName, 'custom_') === 0 && array_search($fieldName, $messageToken['contribution']) !== FALSE) { + $result['values'][$id][$fieldName] = CRM_Core_BAO_CustomField::displayValue($result['values'][$id][$fieldName], $fieldName); + } + } + } + return $result; + } + + /** + * Get invoice_number for contribution. + * + * @param int $contributionID + * + * @return string + */ + public static function getInvoiceNumber($contributionID) { + if ($invoicePrefix = self::checkContributeSettings('invoice_prefix', TRUE)) { + return $invoicePrefix . $contributionID; + } + + return NULL; + } + + /** + * Load the values needed for the event message. + * + * @param int $eventID + * @param int $participantID + * @param int|null $contributionID + * + * @return array + * @throws \CRM_Core_Exception + */ + protected function loadEventMessageTemplateParams(int $eventID, int $participantID, $contributionID): array { + + $eventParams = [ + 'id' => $eventID, + ]; + $values = ['event' => []]; + + CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); + // add custom fields for event + $eventGroupTree = CRM_Core_BAO_CustomGroup::getTree('Event', NULL, $eventID); + + $eventCustomGroup = []; + foreach ($eventGroupTree as $key => $group) { + if ($key === 'info') { + continue; + } + + foreach ($group['fields'] as $k => $customField) { + $groupLabel = $group['title']; + if (!empty($customField['customValue'])) { + foreach ($customField['customValue'] as $customFieldValues) { + $eventCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); + } + } + } + } + $values['event']['customGroup'] = $eventCustomGroup; + + //get participant details + $participantParams = [ + 'id' => $participantID, + ]; + + $values['participant'] = []; + + CRM_Event_BAO_Participant::getValues($participantParams, $values['participant'], $participantIds); + // add custom fields for event + $participantGroupTree = CRM_Core_BAO_CustomGroup::getTree('Participant', NULL, $participantID); + $participantCustomGroup = []; + foreach ($participantGroupTree as $key => $group) { + if ($key === 'info') { + continue; + } + + foreach ($group['fields'] as $k => $customField) { + $groupLabel = $group['title']; + if (!empty($customField['customValue'])) { + foreach ($customField['customValue'] as $customFieldValues) { + $participantCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); + } + } + } + } + $values['participant']['customGroup'] = $participantCustomGroup; + + //get location details + $locationParams = [ + 'entity_id' => $eventID, + 'entity_table' => 'civicrm_event', + ]; + $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); + + $ufJoinParams = [ + 'entity_table' => 'civicrm_event', + 'entity_id' => $eventID, + 'module' => 'CiviEvent', + ]; + + list($custom_pre_id, + $custom_post_ids + ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); + + $values['custom_pre_id'] = $custom_pre_id; + $values['custom_post_id'] = $custom_post_ids; + + // set lineItem for event contribution + if ($contributionID) { + $participantIds = CRM_Event_BAO_Participant::getParticipantIds($contributionID); + if (!empty($participantIds)) { + foreach ($participantIds as $pIDs) { + $lineItem = CRM_Price_BAO_LineItem::getLineItems($pIDs); + if (!CRM_Utils_System::isNull($lineItem)) { + $values['lineItem'][] = $lineItem; + } + } + } + } + return $values; + } + } diff --git a/CRM/Contribute/BAO/Contribution/Utils.php b/CRM/Contribute/BAO/Contribution/Utils.php index 6633d2fe43b6..8ab4f3e822d9 100644 --- a/CRM/Contribute/BAO/Contribution/Utils.php +++ b/CRM/Contribute/BAO/Contribution/Utils.php @@ -1,9 +1,9 @@ id; //CRM-15297 - contributionType is obsolete - pass financial type as well so people can deprecate it $paymentParams['financialType_name'] = $paymentParams['contributionType_name'] = $form->_params['contributionType_name'] = $financialType->name; //CRM-11456 @@ -98,14 +100,31 @@ public static function processConfirm( } if ($isPaymentTransaction) { - $contributionParams = array( + $contributionParams = [ 'id' => CRM_Utils_Array::value('contribution_id', $paymentParams), 'contact_id' => $contactID, 'is_test' => $isTest, - 'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams, CRM_Utils_Array::value('campaign_id', $form->_values)), - 'contribution_page_id' => $form->_id, 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), - ); + ]; + + // CRM-21200: Don't overwrite contribution details during 'Pay now' payment + if (empty($form->_params['contribution_id'])) { + $contributionParams['contribution_page_id'] = $form->_id; + $contributionParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $paymentParams, CRM_Utils_Array::value('campaign_id', $form->_values)); + } + // In case of 'Pay now' payment, append the contribution source with new text 'Paid later via page ID: N.' + else { + // contribution.source only allows 255 characters so we are using ellipsify(...) to ensure it. + $contributionParams['source'] = CRM_Utils_String::ellipsify( + ts('Paid later via page ID: %1. %2', [ + 1 => $form->_id, + 2 => $contributionParams['source'], + ]), + // eventually activity.description append price information to source text so keep it 220 to ensure string length doesn't exceed 255 characters. + 220 + ); + } + if (isset($paymentParams['line_item'])) { // @todo make sure this is consisently set at this point. $contributionParams['line_item'] = $paymentParams['line_item']; @@ -114,6 +133,9 @@ public static function processConfirm( $contributionParams['payment_instrument_id'] = $paymentParams['payment_instrument_id'] = $form->_paymentProcessor['payment_instrument_id']; } + // @todo this is the wrong place for this - it should be done as close to form submission + // as possible + $paymentParams['amount'] = CRM_Utils_Rule::cleanMoney($paymentParams['amount']); $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution( $form, $paymentParams, @@ -127,16 +149,14 @@ public static function processConfirm( $paymentParams['item_name'] = $form->_params['description']; - $paymentParams['qfKey'] = $form->controller->_key; + $paymentParams['qfKey'] = empty($paymentParams['qfKey']) ? $form->controller->_key : $paymentParams['qfKey']; if ($paymentParams['skipLineItem']) { // We are not processing the line item here because we are processing a membership. // Do not continue with contribution processing in this function. - return array('contribution' => $contribution); + return ['contribution' => $contribution]; } $paymentParams['contributionID'] = $contribution->id; - //CRM-15297 deprecate contributionTypeID - $paymentParams['financialTypeID'] = $paymentParams['contributionTypeID'] = $contribution->financial_type_id; $paymentParams['contributionPageID'] = $contribution->contribution_page_id; if (isset($paymentParams['contribution_source'])) { $paymentParams['source'] = $paymentParams['contribution_source']; @@ -210,11 +230,11 @@ public static function processConfirm( // This is kind of a back-up for pay-later $0 transactions. // In other flows they pick up the manual processor & get dealt with above (I // think that might be better...). - return array( + return [ 'payment_status_id' => 1, 'contribution' => $contribution, 'payment_processor_id' => 0, - ); + ]; } CRM_Contribute_BAO_ContributionPage::sendMail($contactID, @@ -225,13 +245,14 @@ public static function processConfirm( /** * Is a payment being made. + * * Note that setting is_monetary on the form is somewhat legacy and the behaviour around this setting is confusing. It would be preferable * to look for the amount only (assuming this cannot refer to payment in goats or other non-monetary currency * @param CRM_Core_Form $form * * @return bool */ - static protected function isPaymentTransaction($form) { + protected static function isPaymentTransaction($form) { return ($form->_amount >= 0.0) ? TRUE : FALSE; } @@ -246,11 +267,11 @@ static protected function isPaymentTransaction($form) { */ public static function contributionChartMonthly($param) { if ($param) { - $param = array(1 => array($param, 'Integer')); + $param = [1 => [$param, 'Integer']]; } else { $param = date("Y"); - $param = array(1 => array($param, 'Integer')); + $param = [1 => [$param, 'Integer']]; } $query = " @@ -365,12 +386,12 @@ public static function _fillCommonParams(&$params, $type = 'paypal') { $params['address'][1]['location_type_id'] = $billingLocTypeId; } if (!CRM_Utils_System::isNull($params['email'])) { - $params['email'] = array( - 1 => array( + $params['email'] = [ + 1 => [ 'email' => $params['email'], 'location_type_id' => $billingLocTypeId, - ), - ); + ], + ]; } if (isset($transaction['trxn_id'])) { @@ -400,7 +421,7 @@ public static function _fillCommonParams(&$params, $type = 'paypal') { } $source = ts('ContributionProcessor: %1 API', - array(1 => ucfirst($type)) + [1 => ucfirst($type)] ); if (isset($transaction['source'])) { $transaction['source'] = $source . ':: ' . $transaction['source']; @@ -421,7 +442,7 @@ public static function getFirstLastDetails($contactID) { static $_cache; if (!$_cache) { - $_cache = array(); + $_cache = []; } if (!isset($_cache[$contactID])) { @@ -432,28 +453,28 @@ public static function getFirstLastDetails($contactID) { ORDER BY receive_date ASC LIMIT 1 "; - $params = array(1 => array($contactID, 'Integer')); + $params = [1 => [$contactID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); - $details = array( + $details = [ 'first' => NULL, 'last' => NULL, - ); + ]; if ($dao->fetch()) { - $details['first'] = array( + $details['first'] = [ 'total_amount' => $dao->total_amount, 'receive_date' => $dao->receive_date, - ); + ]; } // flip asc and desc to get the last query $sql = str_replace('ASC', 'DESC', $sql); $dao = CRM_Core_DAO::executeQuery($sql, $params); if ($dao->fetch()) { - $details['last'] = array( + $details['last'] = [ 'total_amount' => $dao->total_amount, 'receive_date' => $dao->receive_date, - ); + ]; } $_cache[$contactID] = $details; @@ -468,15 +489,24 @@ public static function getFirstLastDetails($contactID) { * Amount of field. * @param float $taxRate * Tax rate of selected financial account for field. + * @param bool $ugWeDoNotKnowIfItNeedsCleaning_Help + * This should ALWAYS BE FALSE and then be removed. A 'clean' money string uses a standardised format + * such as '1000.99' for one thousand $/Euro/CUR and ninety nine cents/units. + * However, we are in the habit of not necessarily doing that so need to grandfather in + * the new expectation. * * @return array * array of tax amount * */ - public static function calculateTaxAmount($amount, $taxRate) { - $taxAmount = array(); + public static function calculateTaxAmount($amount, $taxRate, $ugWeDoNotKnowIfItNeedsCleaning_Help = FALSE) { + $taxAmount = []; + if ($ugWeDoNotKnowIfItNeedsCleaning_Help) { + Civi::log()->warning('Deprecated function, make sure money is in usable format before calling this.', ['civi.tag' => 'deprecated']); + $amount = CRM_Utils_Rule::cleanMoney($amount); + } // There can not be any rounding at this stage - as this is prior to quantity multiplication - $taxAmount['tax_amount'] = ($taxRate / 100) * CRM_Utils_Rule::cleanMoney($amount); + $taxAmount['tax_amount'] = ($taxRate / 100) * $amount; return $taxAmount; } @@ -497,4 +527,150 @@ public static function formatAmount($amount, $decimals = 2) { return number_format((float) round($amount, (int) $decimals), (int) $decimals, '.', ''); } + /** + * Get contribution statuses by entity e.g. contribution, membership or 'participant' + * + * @param string $usedFor + * @param int $id + * Contribution ID + * + * @return array + * Array of contribution statuses in array('status id' => 'label') format + */ + public static function getContributionStatuses($usedFor = 'contribution', $id = NULL) { + if ($usedFor == 'pledge') { + $statusNames = CRM_Pledge_BAO_Pledge::buildOptions('status_id', 'validate'); + } + else { + $statusNames = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'validate'); + } + + $statusNamesToUnset = [ + // For records which represent a data template for a recurring + // contribution that may not yet have a payment. This status should not + // be available from forms. 'Template' contributions should only be created + // in conjunction with a ContributionRecur record, and should have their + // is_template field set to 1. This status excludes them from reports + // that are still ignorant of the is_template field. + 'Template', + ]; + // on create fetch statuses on basis of component + if (!$id) { + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Refunded', + 'Chargeback', + 'Pending refund', + ]); + + // Event registration and New Membership backoffice form support partially paid payment, + // so exclude this status only for 'New Contribution' form + if ($usedFor == 'contribution') { + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'In Progress', + 'Overdue', + 'Partially paid', + ]); + } + elseif ($usedFor == 'participant') { + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Cancelled', + 'Failed', + ]); + } + elseif ($usedFor == 'membership') { + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'In Progress', + 'Overdue', + ]); + } + } + else { + $contributionStatus = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $id, 'contribution_status_id'); + $name = CRM_Utils_Array::value($contributionStatus, $statusNames); + switch ($name) { + case 'Completed': + // [CRM-17498] Removing unsupported status change options. + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Pending', + 'Failed', + 'Partially paid', + 'Pending refund', + ]); + break; + + case 'Cancelled': + case 'Chargeback': + case 'Refunded': + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Pending', + 'Failed', + ]); + break; + + case 'Pending': + case 'In Progress': + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Refunded', + 'Chargeback', + ]); + break; + + case 'Failed': + $statusNamesToUnset = array_merge($statusNamesToUnset, [ + 'Pending', + 'Refunded', + 'Chargeback', + 'Completed', + 'In Progress', + 'Cancelled', + ]); + break; + } + } + + foreach ($statusNamesToUnset as $name) { + unset($statusNames[CRM_Utils_Array::key($name, $statusNames)]); + } + + // based on filtered statuse names fetch the final list of statuses in array('id' => 'label') format + if ($usedFor == 'pledge') { + $statuses = CRM_Pledge_BAO_Pledge::buildOptions('status_id'); + } + else { + $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id'); + } + foreach ($statuses as $statusID => $label) { + if (!array_key_exists($statusID, $statusNames)) { + unset($statuses[$statusID]); + } + } + + return $statuses; + } + + /** + * CRM-8254 / CRM-6907 - override default currency if applicable + * these lines exist to support a non-default currency on the form but are probably + * obsolete & meddling wth the defaultCurrency is not the right approach.... + * + * @param array $params + */ + public static function overrideDefaultCurrency($params) { + $config = CRM_Core_Config::singleton(); + $config->defaultCurrency = CRM_Utils_Array::value('currency', $params, $config->defaultCurrency); + } + + /** + * Get either the public title if set or the title of a contribution page for use in workflow message template. + * @param int $contribution_page_id + * @return string + */ + public static function getContributionPageTitle($contribution_page_id) { + $title = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $contribution_page_id, 'frontend_title'); + if (empty($title)) { + $title = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $contribution_page_id, 'title'); + } + return $title; + } + } diff --git a/CRM/Contribute/BAO/ContributionPage.php b/CRM/Contribute/BAO/ContributionPage.php index f362a07d9a82..f14aa1db8a26 100644 --- a/CRM/Contribute/BAO/ContributionPage.php +++ b/CRM/Contribute/BAO/ContributionPage.php @@ -1,9 +1,9 @@ $id); + $params = ['id' => $id]; CRM_Core_DAO::commonRetrieve('CRM_Contribute_DAO_ContributionPage', $params, $values); // get the profile ids - $ufJoinParams = array( + $ufJoinParams = [ 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $id, - ); + ]; // retrieve profile id as also unserialize module_data corresponding to each $module foreach ($modules as $module) { @@ -150,15 +150,15 @@ public static function setValues($id, &$values) { * @param array $fieldTypes */ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMessageText = FALSE, $fieldTypes = NULL) { - $gIds = array(); - $params = array('custom_pre_id' => array(), 'custom_post_id' => array()); + $gIds = []; + $params = ['custom_pre_id' => [], 'custom_post_id' => []]; $email = NULL; // We are trying to fight the good fight against leaky variables (CRM-17519) so let's get really explicit // about ensuring the variables we want for the template are defined. // @todo add to this until all tpl params are explicit in this function and not waltzing around the codebase. // Next stage is to remove this & ensure there are no e-notices - ie. all are set before they hit this fn. - $valuesRequiredForTemplate = array( + $valuesRequiredForTemplate = [ 'customPre', 'customPost', 'customPre_grouptitle', @@ -168,7 +168,7 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes 'amount', 'receipt_date', 'is_pay_later', - ); + ]; foreach ($valuesRequiredForTemplate as $valueRequiredForTemplate) { if (!isset($values[$valueRequiredForTemplate])) { @@ -179,26 +179,26 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes if (isset($values['custom_pre_id'])) { $preProfileType = CRM_Core_BAO_UFField::getProfileType($values['custom_pre_id']); if ($preProfileType == 'Membership' && !empty($values['membership_id'])) { - $params['custom_pre_id'] = array( - array( + $params['custom_pre_id'] = [ + [ 'member_id', '=', $values['membership_id'], 0, 0, - ), - ); + ], + ]; } elseif ($preProfileType == 'Contribution' && !empty($values['contribution_id'])) { - $params['custom_pre_id'] = array( - array( + $params['custom_pre_id'] = [ + [ 'contribution_id', '=', $values['contribution_id'], 0, 0, - ), - ); + ], + ]; } $gIds['custom_pre_id'] = $values['custom_pre_id']; @@ -207,26 +207,26 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes if (isset($values['custom_post_id'])) { $postProfileType = CRM_Core_BAO_UFField::getProfileType($values['custom_post_id']); if ($postProfileType == 'Membership' && !empty($values['membership_id'])) { - $params['custom_post_id'] = array( - array( + $params['custom_post_id'] = [ + [ 'member_id', '=', $values['membership_id'], 0, 0, - ), - ); + ], + ]; } elseif ($postProfileType == 'Contribution' && !empty($values['contribution_id'])) { - $params['custom_post_id'] = array( - array( + $params['custom_post_id'] = [ + [ 'contribution_id', '=', $values['contribution_id'], 0, 0, - ), - ); + ], + ]; } $gIds['custom_post_id'] = $values['custom_post_id']; @@ -234,48 +234,48 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes if (!empty($values['is_for_organization'])) { if (!empty($values['membership_id'])) { - $params['onbehalf_profile'] = array( - array( + $params['onbehalf_profile'] = [ + [ 'member_id', '=', $values['membership_id'], 0, 0, - ), - ); + ], + ]; } elseif (!empty($values['contribution_id'])) { - $params['onbehalf_profile'] = array( - array( + $params['onbehalf_profile'] = [ + [ 'contribution_id', '=', $values['contribution_id'], 0, 0, - ), - ); + ], + ]; } } //check whether it is a test drive if ($isTest && !empty($params['custom_pre_id'])) { - $params['custom_pre_id'][] = array( + $params['custom_pre_id'][] = [ 'contribution_test', '=', 1, 0, 0, - ); + ]; } if ($isTest && !empty($params['custom_post_id'])) { - $params['custom_post_id'][] = array( + $params['custom_post_id'][] = [ 'contribution_test', '=', 1, 0, 0, - ); + ]; } if (!$returnMessageText && !empty($gIds)) { @@ -328,8 +328,7 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes if ($preID = CRM_Utils_Array::value('custom_pre_id', $values)) { if (!empty($values['related_contact'])) { $preProfileTypes = CRM_Core_BAO_UFGroup::profileGroups($preID); - //@todo - following line should not refer to undefined $postProfileTypes? figure out way to test - if (in_array('Individual', $preProfileTypes) || in_array('Contact', $postProfileTypes)) { + if (in_array('Individual', $preProfileTypes) || in_array('Contact', $preProfileTypes)) { //Take Individual contact ID $userID = CRM_Utils_Array::value('related_contact', $values); } @@ -349,7 +348,7 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes } if (isset($values['honor'])) { $honorValues = $values['honor']; - $template->_values = array('honoree_profile_id' => $values['honoree_profile_id']); + $template->_values = ['honoree_profile_id' => $values['honoree_profile_id']]; CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields( $template, $honorValues['honor_profile_values'], @@ -357,11 +356,11 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes ); } - $title = isset($values['title']) ? $values['title'] : CRM_Contribute_PseudoConstant::contributionPage($values['contribution_page_id']); + $title = isset($values['title']) ? $values['title'] : CRM_Contribute_BAO_Contribution_Utils::getContributionPageTitle($values['contribution_page_id']); // Set email variables explicitly to avoid leaky smarty variables. // All of these will be assigned to the template, replacing any that might be assigned elsewhere. - $tplParams = array( + $tplParams = [ 'email' => $email, 'receiptFromEmail' => CRM_Utils_Array::value('receipt_from_email', $values), 'contactID' => $contactID, @@ -385,7 +384,9 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes 'is_pay_later' => $values['is_pay_later'], 'receipt_date' => !$values['receipt_date'] ? NULL : date('YmdHis', strtotime($values['receipt_date'])), 'pay_later_receipt' => CRM_Utils_Array::value('pay_later_receipt', $values), - ); + 'honor_block_is_active' => CRM_Utils_Array::value('honor_block_is_active', $values), + 'contributionStatus' => CRM_Utils_Array::value('contribution_status', $values), + ]; if ($contributionTypeId = CRM_Utils_Array::value('financial_type_id', $values)) { $tplParams['financialTypeId'] = $contributionTypeId; @@ -430,23 +431,23 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes } // use either the contribution or membership receipt, based on whether it’s a membership-related contrib or not - $sendTemplateParams = array( + $sendTemplateParams = [ 'groupName' => !empty($values['isMembership']) ? 'msg_tpl_workflow_membership' : 'msg_tpl_workflow_contribution', 'valueName' => !empty($values['isMembership']) ? 'membership_online_receipt' : 'contribution_online_receipt', 'contactId' => $contactID, 'tplParams' => $tplParams, 'isTest' => $isTest, 'PDFFilename' => 'receipt.pdf', - ); + ]; if ($returnMessageText) { list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); - return array( + return [ 'subject' => $subject, 'body' => $message, 'to' => $displayName, 'html' => $html, - ); + ]; } if (empty($values['receipt_from_name']) && empty($values['receipt_from_name'])) { @@ -499,9 +500,9 @@ public static function sendMail($contactID, $values, $isTest = FALSE, $returnMes * * @return array */ - protected static function getProfileNameAndFields($gid, $cid, &$params, $fieldTypes = array()) { + protected static function getProfileNameAndFields($gid, $cid, &$params, $fieldTypes = []) { $groupTitle = NULL; - $values = array(); + $values = []; if ($gid) { if (CRM_Core_BAO_UFGroup::filterUFGroups($gid, $cid)) { $fields = CRM_Core_BAO_UFGroup::getFields($gid, FALSE, CRM_Core_Action::VIEW, NULL, NULL, FALSE, NULL, FALSE, NULL, CRM_Core_Permission::CREATE, NULL); @@ -526,7 +527,7 @@ protected static function getProfileNameAndFields($gid, $cid, &$params, $fieldTy CRM_Core_BAO_UFGroup::getValues($cid, $fields, $values, FALSE, $params); } } - return array($groupTitle, $values); + return [$groupTitle, $values]; } /** @@ -543,17 +544,17 @@ protected static function getProfileNameAndFields($gid, $cid, &$params, $fieldTy * @param bool|object $autoRenewMembership is it a auto renew membership. */ public static function recurringNotify($type, $contactID, $pageID, $recur, $autoRenewMembership = FALSE) { - $value = array(); + $value = []; $isEmailReceipt = FALSE; if ($pageID) { - CRM_Core_DAO::commonRetrieveAll('CRM_Contribute_DAO_ContributionPage', 'id', $pageID, $value, array( + CRM_Core_DAO::commonRetrieveAll('CRM_Contribute_DAO_ContributionPage', 'id', $pageID, $value, [ 'title', 'is_email_receipt', 'receipt_from_name', 'receipt_from_email', 'cc_receipt', 'bcc_receipt', - )); + ]); $isEmailReceipt = CRM_Utils_Array::value('is_email_receipt', $value[$pageID]); } elseif ($recur->id) { @@ -577,11 +578,11 @@ public static function recurringNotify($type, $contactID, $pageID, $recur, $auto } list($displayName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID, FALSE); - $templatesParams = array( + $templatesParams = [ 'groupName' => 'msg_tpl_workflow_contribution', 'valueName' => 'contribution_recurring_notify', 'contactId' => $contactID, - 'tplParams' => array( + 'tplParams' => [ 'recur_frequency_interval' => $recur->frequency_interval, 'recur_frequency_unit' => $recur->frequency_unit, 'recur_installments' => $recur->installments, @@ -593,11 +594,11 @@ public static function recurringNotify($type, $contactID, $pageID, $recur, $auto 'receipt_from_name' => $receiptFromName, 'receipt_from_email' => $receiptFromEmail, 'auto_renew_membership' => $autoRenewMembership, - ), + ], 'from' => $receiptFrom, 'toName' => $displayName, 'toEmail' => $email, - ); + ]; //CRM-13811 if ($pageID) { $templatesParams['cc'] = CRM_Utils_Array::value('cc_receipt', $value[$pageID]); @@ -665,81 +666,87 @@ public static function buildCustomDisplay($gid, $name, $cid, &$template, &$param * @return CRM_Contribute_DAO_ContributionPage */ public static function copy($id) { - $fieldsFix = array( - 'prefix' => array( + $session = CRM_Core_Session::singleton(); + + $fieldsFix = [ + 'prefix' => [ 'title' => ts('Copy of') . ' ', - ), - ); - $copy = &CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_ContributionPage', array( + ], + 'replace' => [ + 'created_id' => $session->get('userID'), + 'created_date' => date('YmdHis'), + ], + ]; + $copy = CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_ContributionPage', [ 'id' => $id, - ), NULL, $fieldsFix); + ], NULL, $fieldsFix); //copying all the blocks pertaining to the contribution page - $copyPledgeBlock = &CRM_Core_DAO::copyGeneric('CRM_Pledge_DAO_PledgeBlock', array( + $copyPledgeBlock = CRM_Core_DAO::copyGeneric('CRM_Pledge_DAO_PledgeBlock', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, - )); + ]); - $copyMembershipBlock = &CRM_Core_DAO::copyGeneric('CRM_Member_DAO_MembershipBlock', array( + $copyMembershipBlock = CRM_Core_DAO::copyGeneric('CRM_Member_DAO_MembershipBlock', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, - )); + ]); - $copyUFJoin = &CRM_Core_DAO::copyGeneric('CRM_Core_DAO_UFJoin', array( + $copyUFJoin = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_UFJoin', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, - )); + ]); - $copyWidget = &CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_Widget', array( + $copyWidget = CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_Widget', [ 'contribution_page_id' => $id, - ), array( + ], [ 'contribution_page_id' => $copy->id, - )); + ]); //copy price sets CRM_Price_BAO_PriceSet::copyPriceSet('civicrm_contribution_page', $id, $copy->id); - $copyTellFriend = &CRM_Core_DAO::copyGeneric('CRM_Friend_DAO_Friend', array( + $copyTellFriend = CRM_Core_DAO::copyGeneric('CRM_Friend_DAO_Friend', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, - )); + ]); - $copyPersonalCampaignPages = &CRM_Core_DAO::copyGeneric('CRM_PCP_DAO_PCPBlock', array( + $copyPersonalCampaignPages = CRM_Core_DAO::copyGeneric('CRM_PCP_DAO_PCPBlock', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, 'target_entity_id' => $copy->id, - )); + ]); - $copyPremium = &CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_Premium', array( + $copyPremium = CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_Premium', [ 'entity_id' => $id, 'entity_table' => 'civicrm_contribution_page', - ), array( + ], [ 'entity_id' => $copy->id, - )); + ]); $premiumQuery = " SELECT id FROM civicrm_premiums WHERE entity_table = 'civicrm_contribution_page' AND entity_id ={$id}"; - $premiumDao = CRM_Core_DAO::executeQuery($premiumQuery, CRM_Core_DAO::$_nullArray); + $premiumDao = CRM_Core_DAO::executeQuery($premiumQuery); while ($premiumDao->fetch()) { if ($premiumDao->id) { - CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_PremiumsProduct', array( + CRM_Core_DAO::copyGeneric('CRM_Contribute_DAO_PremiumsProduct', [ 'premiums_id' => $premiumDao->id, - ), array( + ], [ 'premiums_id' => $copyPremium->id, - )); + ]); } } @@ -757,14 +764,14 @@ public static function copy($id) { * @return array * info regarding all sections. */ - public static function getSectionInfo($contribPageIds = array()) { - $info = array(); + public static function getSectionInfo($contribPageIds = []) { + $info = []; $whereClause = NULL; if (is_array($contribPageIds) && !empty($contribPageIds)) { $whereClause = 'WHERE civicrm_contribution_page.id IN ( ' . implode(', ', $contribPageIds) . ' )'; } - $sections = array( + $sections = [ 'settings', 'amount', 'membership', @@ -774,7 +781,7 @@ public static function getSectionInfo($contribPageIds = array()) { 'pcp', 'widget', 'premium', - ); + ]; $query = " SELECT civicrm_contribution_page.id as id, civicrm_contribution_page.financial_type_id as settings, @@ -832,8 +839,8 @@ public static function getSectionInfo($contribPageIds = array()) { * * @return array|bool */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { - $params = array(); + public static function buildOptions($fieldName, $context = NULL, $props = []) { + $params = []; // Special logic for fields whose options depend on context or properties switch ($fieldName) { case 'financial_type_id': @@ -861,21 +868,21 @@ public static function formatModuleData($params, $setDefault = FALSE, $module) { $domain = new CRM_Core_DAO_Domain(); $domain->find(TRUE); - $moduleDataFormat = array( - 'soft_credit' => array( + $moduleDataFormat = [ + 'soft_credit' => [ 1 => 'soft_credit_types', - 'multilingual' => array( + 'multilingual' => [ 'honor_block_title', 'honor_block_text', - ), - ), - 'on_behalf' => array( + ], + ], + 'on_behalf' => [ 1 => 'is_for_organization', - 'multilingual' => array( + 'multilingual' => [ 'for_organization', - ), - ), - ); + ], + ], + ]; //When we are fetching the honor params respecting both multi and mono lingual state //and setting it to default param of Contribution Page's Main and Setting form @@ -900,10 +907,10 @@ public static function formatModuleData($params, $setDefault = FALSE, $module) { //check and handle multilingual honoree params if (!$domain->locales) { //if in singlelingual state simply return the array format - $json = array($module => NULL); + $json = [$module => NULL]; foreach ($moduleDataFormat[$module] as $key => $attribute) { if ($key === 'multilingual') { - $json[$module]['default'] = array(); + $json[$module]['default'] = []; foreach ($attribute as $attr) { $json[$module]['default'][$attr] = $params[$attr]; } @@ -917,10 +924,10 @@ public static function formatModuleData($params, $setDefault = FALSE, $module) { else { //if in multilingual state then retrieve the module_data against this contribution and //merge with earlier module_data json data to current so not to lose earlier multilingual module_data information - $json = array($module => NULL); + $json = [$module => NULL]; foreach ($moduleDataFormat[$module] as $key => $attribute) { if ($key === 'multilingual') { - $json[$module][$config->lcMessages] = array(); + $json[$module][$config->lcMessages] = []; foreach ($attribute as $attr) { $json[$module][$config->lcMessages][$attr] = $params[$attr]; } @@ -952,12 +959,12 @@ public static function formatModuleData($params, $setDefault = FALSE, $module) { * @return array */ public static function addInvoicePdfToEmail($contributionId, $userID) { - $contributionID = array($contributionId); - $contactId = array($userID); - $pdfParams = array( + $contributionID = [$contributionId]; + $contactId = [$userID]; + $pdfParams = [ 'output' => 'pdf_invoice', 'forPage' => 'confirmpage', - ); + ]; $pdfHtml = CRM_Contribute_Form_Task_Invoice::printPDF($contributionID, $pdfParams, $contactId); return $pdfHtml; } @@ -971,11 +978,11 @@ public static function addInvoicePdfToEmail($contributionId, $userID) { * isSeparateMembershipPayment */ public static function getIsMembershipPayment($id) { - $membershipBlocks = civicrm_api3('membership_block', 'get', array( - 'entity_table' => 'civicrm_contribution_page', - 'entity_id' => $id, - 'sequential' => TRUE, - )); + $membershipBlocks = civicrm_api3('membership_block', 'get', [ + 'entity_table' => 'civicrm_contribution_page', + 'entity_id' => $id, + 'sequential' => TRUE, + ]); if (!$membershipBlocks['count']) { return FALSE; } diff --git a/CRM/Contribute/BAO/ContributionRecur.php b/CRM/Contribute/BAO/ContributionRecur.php index 24937e2af971..38e7cb999a7f 100644 --- a/CRM/Contribute/BAO/ContributionRecur.php +++ b/CRM/Contribute/BAO/ContributionRecur.php @@ -1,9 +1,9 @@ push(CRM_Core_Error::DUPLICATE_CONTRIBUTION, 'Fatal', - array($d), + [$d], "Found matching recurring contribution(s): $d" ); return $error; @@ -89,7 +89,7 @@ public static function add(&$params) { $config = CRM_Core_Config::singleton(); $recurring->currency = $config->defaultCurrency; } - $result = $recurring->save(); + $recurring->save(); if (!empty($params['id'])) { CRM_Utils_Hook::post('edit', 'ContributionRecur', $recurring->id, $recurring); @@ -104,7 +104,7 @@ public static function add(&$params) { CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution_recur', $recurring->id); } - return $result; + return $recurring; } /** @@ -123,17 +123,17 @@ public static function checkDuplicate($params, &$duplicates) { $trxn_id = CRM_Utils_Array::value('trxn_id', $params); $invoice_id = CRM_Utils_Array::value('invoice_id', $params); - $clause = array(); - $params = array(); + $clause = []; + $params = []; if ($trxn_id) { $clause[] = "trxn_id = %1"; - $params[1] = array($trxn_id, 'String'); + $params[1] = [$trxn_id, 'String']; } if ($invoice_id) { $clause[] = "invoice_id = %2"; - $params[2] = array($invoice_id, 'String'); + $params[2] = [$invoice_id, 'String']; } if (empty($clause)) { @@ -143,7 +143,7 @@ public static function checkDuplicate($params, &$duplicates) { $clause = implode(' OR ', $clause); if ($id) { $clause = "( $clause ) AND id != %3"; - $params[3] = array($id, 'Integer'); + $params[3] = [$id, 'Integer']; } $query = "SELECT id FROM civicrm_contribution_recur WHERE $clause"; @@ -160,25 +160,44 @@ public static function checkDuplicate($params, &$duplicates) { * Get the payment processor (array) for a recurring processor. * * @param int $id - * @param string $mode - * - Test or NULL - all other variants are ignored. * * @return array|null */ - public static function getPaymentProcessor($id, $mode = NULL) { - $sql = " -SELECT r.payment_processor_id - FROM civicrm_contribution_recur r - WHERE r.id = %1"; - $params = array(1 => array($id, 'Integer')); - $paymentProcessorID = CRM_Core_DAO::singleValueQuery($sql, - $params - ); - if (!$paymentProcessorID) { - return NULL; - } + public static function getPaymentProcessor($id) { + $paymentProcessorID = self::getPaymentProcessorID($id); + return CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID); + } - return CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID, $mode); + /** + * Get the processor object for the recurring contribution record. + * + * @param int $id + * + * @return CRM_Core_Payment|NULL + * Returns a processor object or NULL if the processor is disabled. + * Note this returns the 'Manual' processor object if no processor is attached + * (since it still makes sense to update / cancel + */ + public static function getPaymentProcessorObject($id) { + $processor = self::getPaymentProcessor($id); + return is_array($processor) ? $processor['object'] : NULL; + } + + /** + * Get the payment processor for the given recurring contribution. + * + * @param int $recurID + * + * @return int + * Payment processor id. If none found return 0 which represents the + * pseudo processor used for pay-later. + */ + public static function getPaymentProcessorID($recurID) { + $recur = civicrm_api3('ContributionRecur', 'getsingle', [ + 'id' => $recurID, + 'return' => ['payment_processor_id'], + ]); + return (int) CRM_Utils_Array::value('payment_processor_id', $recur, 0); } /** @@ -192,7 +211,7 @@ public static function getPaymentProcessor($id, $mode = NULL) { */ public static function getCount(&$ids) { $recurID = implode(',', $ids); - $totalCount = array(); + $totalCount = []; $query = " SELECT contribution_recur_id, count( contribution_recur_id ) as commpleted @@ -231,36 +250,41 @@ public static function deleteRecurContribution($recurId) { /** * Cancel Recurring contribution. * - * @param int $recurId - * Recur contribution id. - * @param array $objects - * An array of objects that is to be cancelled like. - * contribution, membership, event. At least contribution object is a must. - * - * @param array $activityParams + * @param array $params + * Recur contribution params * * @return bool */ - public static function cancelRecurContribution($recurId, $objects, $activityParams = array()) { + public static function cancelRecurContribution($params) { + if (is_numeric($params)) { + CRM_Core_Error::deprecatedFunctionWarning('You are using a BAO function whose signature has changed. Please use the ContributionRecur.cancel api'); + $params = ['id' => $params]; + } + $recurId = $params['id']; if (!$recurId) { return FALSE; } + $activityParams = [ + 'subject' => !empty($params['membership_id']) ? ts('Auto-renewal membership cancelled') : ts('Recurring contribution cancelled'), + 'details' => CRM_Utils_Array::value('processor_message', $params), + ]; - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - $canceledId = array_search('Cancelled', $contributionStatus); + $cancelledId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', 'Cancelled'); $recur = new CRM_Contribute_DAO_ContributionRecur(); $recur->id = $recurId; - $recur->whereAdd("contribution_status_id != $canceledId"); + $recur->whereAdd("contribution_status_id != $cancelledId"); if ($recur->find(TRUE)) { $transaction = new CRM_Core_Transaction(); - $recur->contribution_status_id = $canceledId; + $recur->contribution_status_id = $cancelledId; $recur->start_date = CRM_Utils_Date::isoToMysql($recur->start_date); $recur->create_date = CRM_Utils_Date::isoToMysql($recur->create_date); $recur->modified_date = CRM_Utils_Date::isoToMysql($recur->modified_date); + $recur->cancel_reason = CRM_Utils_Array::value('cancel_reason', $params); $recur->cancel_date = date('YmdHis'); $recur->save(); + // @fixme https://lab.civicrm.org/dev/core/issues/927 Cancelling membership etc is not desirable for all use-cases and we should be able to disable it $dao = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurId); if ($dao && $dao->recur_id) { $details = CRM_Utils_Array::value('details', $activityParams); @@ -270,50 +294,39 @@ public static function cancelRecurContribution($recurId, $objects, $activityPara $membershipType = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $dao->membership_id, 'membership_type_id'); $membershipType = CRM_Utils_Array::value($membershipType, $membershipTypes); $details .= ' -
    ' . ts('Automatic renewal of %1 membership cancelled.', array(1 => $membershipType)); +
    ' . ts('Automatic renewal of %1 membership cancelled.', [1 => $membershipType]); } else { - $details .= ' -
    ' . ts('The recurring contribution of %1, every %2 %3 has been cancelled.', array( - 1 => $dao->amount, - 2 => $dao->frequency_interval, - 3 => $dao->frequency_unit, - )); + $details .= '
    ' . ts('The recurring contribution of %1, every %2 %3 has been cancelled.', [ + 1 => $dao->amount, + 2 => $dao->frequency_interval, + 3 => $dao->frequency_unit, + ]); } - $activityParams = array( + $activityParams = [ 'source_contact_id' => $dao->contact_id, - 'source_record_id' => CRM_Utils_Array::value('source_record_id', $activityParams), - 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Cancel Recurring Contribution'), + 'source_record_id' => $dao->recur_id, + 'activity_type_id' => 'Cancel Recurring Contribution', 'subject' => CRM_Utils_Array::value('subject', $activityParams, ts('Recurring contribution cancelled')), 'details' => $details, - 'activity_date_time' => date('YmdHis'), - 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), - ); - $session = CRM_Core_Session::singleton(); - $cid = $session->get('userID'); + 'status_id' => 'Completed', + ]; + + $cid = CRM_Core_Session::singleton()->get('userID'); if ($cid) { $activityParams['target_contact_id'][] = $activityParams['source_contact_id']; $activityParams['source_contact_id'] = $cid; } - // @todo use the api & do less wrangling above - CRM_Activity_BAO_Activity::create($activityParams); + civicrm_api3('Activity', 'create', $activityParams); } - // if there are associated objects, cancel them as well - if (!$objects) { - $transaction->commit(); - return TRUE; - } - else { - // @todo - this is bad! Get the function out of the ipn. - $baseIPN = new CRM_Core_Payment_BaseIPN(); - return $baseIPN->cancelled($objects, $transaction); - } + $transaction->commit(); + return TRUE; } else { // if already cancelled, return true $recur->whereAdd(); - $recur->whereAdd("contribution_status_id = $canceledId"); + $recur->whereAdd("contribution_status_id = $cancelledId"); if ($recur->find(TRUE)) { return TRUE; } @@ -322,43 +335,6 @@ public static function cancelRecurContribution($recurId, $objects, $activityPara return FALSE; } - /** - * Get list of recurring contribution of contact Ids. - * - * @param int $contactId - * Contact ID. - * - * @return array - * list of recurring contribution fields - * - */ - public static function getRecurContributions($contactId) { - $params = array(); - $recurDAO = new CRM_Contribute_DAO_ContributionRecur(); - $recurDAO->contact_id = $contactId; - $recurDAO->find(); - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(); - - while ($recurDAO->fetch()) { - $params[$recurDAO->id]['id'] = $recurDAO->id; - $params[$recurDAO->id]['contactId'] = $recurDAO->contact_id; - $params[$recurDAO->id]['start_date'] = $recurDAO->start_date; - $params[$recurDAO->id]['end_date'] = $recurDAO->end_date; - $params[$recurDAO->id]['next_sched_contribution_date'] = $recurDAO->next_sched_contribution_date; - $params[$recurDAO->id]['amount'] = $recurDAO->amount; - $params[$recurDAO->id]['currency'] = $recurDAO->currency; - $params[$recurDAO->id]['frequency_unit'] = $recurDAO->frequency_unit; - $params[$recurDAO->id]['frequency_interval'] = $recurDAO->frequency_interval; - $params[$recurDAO->id]['installments'] = $recurDAO->installments; - $params[$recurDAO->id]['contribution_status_id'] = $recurDAO->contribution_status_id; - $params[$recurDAO->id]['contribution_status'] = CRM_Utils_Array::value($recurDAO->contribution_status_id, $contributionStatus); - $params[$recurDAO->id]['is_test'] = $recurDAO->is_test; - $params[$recurDAO->id]['payment_processor_id'] = $recurDAO->payment_processor_id; - } - - return $params; - } - /** * @param int $entityID * @param string $entity @@ -409,7 +385,7 @@ public static function getSubscriptionDetails($entityID, $entity = 'recur') { WHERE mp.membership_id = %1"; } - $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($entityID, 'Integer'))); + $dao = CRM_Core_DAO::executeQuery($sql, [1 => [$entityID, 'Integer']]); if ($dao->fetch()) { return $dao; } @@ -451,19 +427,36 @@ public static function supportsFinancialTypeChange($id) { * @return array * @throws \CiviCRM_API3_Exception */ - public static function getTemplateContribution($id, $overrides = array()) { - $templateContribution = civicrm_api3('Contribution', 'get', array( - 'contribution_recur_id' => $id, - 'options' => array('limit' => 1, 'sort' => array('id DESC')), - 'sequential' => 1, - 'contribution_test' => '', - )); - if ($templateContribution['count']) { - $result = array_merge($templateContribution['values'][0], $overrides); + public static function getTemplateContribution($id, $overrides = []) { + // use api3 because api4 doesn't handle ContributionRecur yet... + $is_test = civicrm_api3('ContributionRecur', 'getvalue', [ + 'return' => "is_test", + 'id' => $id, + ]); + // First look for new-style template contribution with is_template=1 + $templateContributions = \Civi\Api4\Contribution::get() + ->addWhere('contribution_recur_id', '=', $id) + ->addWhere('is_template', '=', 1) + ->addWhere('is_test', '=', $is_test) + ->addOrderBy('id', 'DESC') + ->setLimit(1) + ->execute(); + if (!$templateContributions->count()) { + // Fall back to old style template contributions + $templateContributions = \Civi\Api4\Contribution::get() + ->addWhere('contribution_recur_id', '=', $id) + ->addWhere('is_test', '=', $is_test) + ->addOrderBy('id', 'DESC') + ->setLimit(1) + ->execute(); + } + if ($templateContributions->count()) { + $templateContribution = $templateContributions->first(); + $result = array_merge($templateContribution, $overrides); $result['line_item'] = CRM_Contribute_BAO_ContributionRecur::calculateRecurLineItems($id, $result['total_amount'], $result['financial_type_id']); return $result; } - return array(); + return []; } public static function setSubscriptionContext() { @@ -474,7 +467,7 @@ public static function setSubscriptionContext() { $cid = CRM_Utils_Request::retrieve('cid', 'Integer'); $mid = CRM_Utils_Request::retrieve('mid', 'Integer'); $qfkey = CRM_Utils_Request::retrieve('key', 'String'); - $context = CRM_Utils_Request::retrieve('context', 'String'); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric'); if ($cid) { switch ($context) { case 'contribution': @@ -564,7 +557,7 @@ public static function sendRecurringStartOrEndNotification($ids, $recur, $isFirs * @param int $recurId * @param int $targetContributionId */ - static public function copyCustomValues($recurId, $targetContributionId) { + public static function copyCustomValues($recurId, $targetContributionId) { if ($recurId && $targetContributionId) { // get the initial contribution id of recur id $sourceContributionId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $recurId, 'id', 'contribution_recur_id'); @@ -580,11 +573,11 @@ static public function copyCustomValues($recurId, $targetContributionId) { } // copy custom data - $extends = array('Contribution'); + $extends = ['Contribution']; $groupTree = CRM_Core_BAO_CustomGroup::getGroupDetail(NULL, NULL, $extends); if ($groupTree) { foreach ($groupTree as $groupID => $group) { - $table[$groupTree[$groupID]['table_name']] = array('entity_id'); + $table[$groupTree[$groupID]['table_name']] = ['entity_id']; foreach ($group['fields'] as $fieldID => $field) { $table[$groupTree[$groupID]['table_name']][] = $groupTree[$groupID]['fields'][$fieldID]['column_name']; } @@ -628,7 +621,7 @@ public static function addrecurSoftCredit($recurId, $targetContributionId) { * Add line items for recurring contribution. * * @param int $recurId - * @param $contribution + * @param \CRM_Contribute_BAO_Contribution $contribution * * @return array */ @@ -643,11 +636,11 @@ public static function addRecurLineItems($recurId, $contribution) { try { // @todo this should be done by virtue of editing the line item as this link // is deprecated. This may be the case but needs testing. - civicrm_api3('membership_payment', 'create', array( + civicrm_api3('membership_payment', 'create', [ 'membership_id' => $value['entity_id'], 'contribution_id' => $contribution->id, 'is_transactional' => FALSE, - )); + ]); } catch (CiviCRM_API3_Exception $e) { // we are catching & ignoring errors as an extra precaution since lost IPNs may be more serious that lost membership_payment data @@ -685,8 +678,8 @@ public static function addRecurLineItems($recurId, $contribution) { * @throws \CiviCRM_API3_Exception */ public static function updateRecurLinkedPledge($contributionID, $contributionRecurID, $contributionStatusID, $contributionAmount) { - $returnProperties = array('id', 'pledge_id'); - $paymentDetails = $paymentIDs = array(); + $returnProperties = ['id', 'pledge_id']; + $paymentDetails = $paymentIDs = []; if (CRM_Core_DAO::commonRetrieveAll('CRM_Pledge_DAO_PledgePayment', 'contribution_id', $contributionID, $paymentDetails, $returnProperties @@ -746,18 +739,17 @@ public static function updateRecurLinkedPledge($contributionID, $contributionRec } /** - * @param $form + * @param CRM_Core_Form $form */ public static function recurringContribution(&$form) { // Recurring contribution fields - foreach (self::getRecurringFields() as $key => $label) { + foreach (self::getRecurringFields() as $key) { if ($key == 'contribution_recur_payment_made' && !empty($form->_formValues) && !CRM_Utils_System::isNull(CRM_Utils_Array::value($key, $form->_formValues)) ) { $form->assign('contribution_recur_pane_open', TRUE); break; } - CRM_Core_Form_Date::buildDateRange($form, $key, 1, '_low', '_high'); // If data has been entered for a recurring field, tell the tpl layer to open the pane if (!empty($form->_formValues) && !empty($form->_formValues[$key . '_relative']) || !empty($form->_formValues[$key . '_low']) || !empty($form->_formValues[$key . '_high'])) { $form->assign('contribution_recur_pane_open', TRUE); @@ -765,41 +757,71 @@ public static function recurringContribution(&$form) { } } + // If values have been supplied for recurring contribution fields, open the recurring contributions pane. + foreach (['contribution_status_id', 'payment_processor_id', 'processor_id', 'trxn_id'] as $fieldName) { + if (!empty($form->_formValues['contribution_recur_' . $fieldName])) { + $form->assign('contribution_recur_pane_open', TRUE); + break; + } + } + // Add field to check if payment is made for recurring contribution - $recurringPaymentOptions = array( + $recurringPaymentOptions = [ 1 => ts('All recurring contributions'), 2 => ts('Recurring contributions with at least one payment'), + ]; + $form->addRadio('contribution_recur_payment_made', NULL, $recurringPaymentOptions, ['allowClear' => TRUE]); + + // Add field for contribution status + $form->addSelect('contribution_recur_contribution_status_id', + ['entity' => 'contribution', 'multiple' => 'multiple', 'context' => 'search', 'options' => CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'search')] ); - $form->addRadio('contribution_recur_payment_made', NULL, $recurringPaymentOptions, array('allowClear' => TRUE)); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_start_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_end_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_modified_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_next_sched_contribution_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_failure_retry_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); - CRM_Core_Form_Date::buildDateRange($form, 'contribution_recur_cancel_date', 1, '_low', '_high', ts('From'), FALSE, FALSE, 'birth'); + $form->addElement('text', 'contribution_recur_processor_id', ts('Processor ID'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionRecur', 'processor_id')); $form->addElement('text', 'contribution_recur_trxn_id', ts('Transaction ID'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionRecur', 'trxn_id')); - CRM_Core_BAO_Query::addCustomFormFields($form, array('ContributionRecur')); + $paymentProcessorOpts = CRM_Contribute_BAO_ContributionRecur::buildOptions('payment_processor_id', 'get'); + $form->add('select', 'contribution_recur_payment_processor_id', ts('Payment Processor ID'), $paymentProcessorOpts, FALSE, ['class' => 'crm-select2', 'multiple' => 'multiple']); + + CRM_Core_BAO_Query::addCustomFormFields($form, ['ContributionRecur']); } + /** + * Get the metadata for fields to be included on the search form. + * + * @throws \CiviCRM_API3_Exception + */ + public static function getContributionRecurSearchFieldMetadata() { + $fields = [ + 'contribution_recur_start_date', + 'contribution_recur_next_sched_contribution_date', + 'contribution_recur_cancel_date', + 'contribution_recur_end_date', + 'contribution_recur_create_date', + 'contribution_recur_modified_date', + 'contribution_recur_failure_retry_date', + ]; + $metadata = civicrm_api3('ContributionRecur', 'getfields', [])['values']; + return array_intersect_key($metadata, array_flip($fields)); + } + /** * Get fields for recurring contributions. * * @return array */ public static function getRecurringFields() { - return array( - 'contribution_recur_payment_made' => ts(''), - 'contribution_recur_start_date' => ts('Recurring Contribution Start Date'), - 'contribution_recur_next_sched_contribution_date' => ts('Next Scheduled Recurring Contribution'), - 'contribution_recur_cancel_date' => ts('Recurring Contribution Cancel Date'), - 'contribution_recur_end_date' => ts('Recurring Contribution End Date'), - 'contribution_recur_create_date' => ('Recurring Contribution Create Date'), - 'contribution_recur_modified_date' => ('Recurring Contribution Modified Date'), - 'contribution_recur_failure_retry_date' => ts('Failed Recurring Contribution Retry Date'), - ); + return [ + 'contribution_recur_payment_made', + 'contribution_recur_start_date', + 'contribution_recur_next_sched_contribution_date', + 'contribution_recur_cancel_date', + 'contribution_recur_end_date', + 'contribution_recur_create_date', + 'contribution_recur_modified_date', + 'contribution_recur_failure_retry_date', + ]; } /** @@ -812,27 +834,27 @@ public static function getRecurringFields() { * Payment status - this correlates to the machine name of the contribution status ID ie * - Completed * - Failed + * @param string $effectiveDate * * @throws \CiviCRM_API3_Exception */ public static function updateOnNewPayment($recurringContributionID, $paymentStatus, $effectiveDate) { - if (empty($effectiveDate)) { - $effectiveDate = date('Y-m-d'); - } - if (!in_array($paymentStatus, array('Completed', 'Failed'))) { + + $effectiveDate = $effectiveDate ? date('Y-m-d', strtotime($effectiveDate)) : date('Y-m-d'); + if (!in_array($paymentStatus, ['Completed', 'Failed'])) { return; } - $params = array( + $params = [ 'id' => $recurringContributionID, - 'return' => array( + 'return' => [ 'contribution_status_id', 'next_sched_contribution_date', 'frequency_unit', 'frequency_interval', 'installments', 'failure_count', - ), - ); + ], + ]; $existing = civicrm_api3('ContributionRecur', 'getsingle', $params); @@ -871,10 +893,10 @@ public static function updateOnNewPayment($recurringContributionID, $paymentStat */ protected static function isComplete($recurringContributionID, $installments) { $paidInstallments = CRM_Core_DAO::singleValueQuery( - 'SELECT count(*) FROM civicrm_contribution + 'SELECT count(*) FROM civicrm_contribution WHERE contribution_recur_id = %1 AND contribution_status_id = ' . CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'), - array(1 => array($recurringContributionID, 'Integer')) + [1 => [$recurringContributionID, 'Integer']] ); if ($paidInstallments >= $installments) { return TRUE; @@ -892,11 +914,20 @@ protected static function isComplete($recurringContributionID, $installments) { * @return array */ public static function calculateRecurLineItems($recurId, $total_amount, $financial_type_id) { - $originalContributionID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $recurId, 'id', 'contribution_recur_id'); - $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($originalContributionID); - $lineSets = array(); + $originalContribution = civicrm_api3('Contribution', 'getsingle', [ + 'contribution_recur_id' => $recurId, + 'contribution_test' => '', + 'options' => ['limit' => 1], + 'return' => ['id', 'financial_type_id'], + ]); + $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($originalContribution['id']); + $lineSets = []; if (count($lineItems) == 1) { foreach ($lineItems as $index => $lineItem) { + if ($lineItem['financial_type_id'] != $originalContribution['financial_type_id']) { + // CRM-20685, Repeattransaction produces incorrect Financial Type ID (in specific circumstance) - if number of lineItems = 1, So this conditional will set the financial_type_id as the original if line_item and contribution comes with different data. + $financial_type_id = $lineItem['financial_type_id']; + } if ($financial_type_id) { // CRM-17718 allow for possibility of changed financial type ID having been set prior to calling this. $lineItem['financial_type_id'] = $financial_type_id; @@ -910,17 +941,70 @@ public static function calculateRecurLineItems($recurId, $total_amount, $financi $priceField = new CRM_Price_DAO_PriceField(); $priceField->id = $lineItem['price_field_id']; $priceField->find(TRUE); - $lineSets[$priceField->price_set_id][] = $lineItem; + $lineSets[$priceField->price_set_id][$lineItem['price_field_id']] = $lineItem; } } // CRM-19309 if more than one then just pass them through: elseif (count($lineItems) > 1) { foreach ($lineItems as $index => $lineItem) { - $lineSets[$index][] = $lineItem; + $lineSets[$index][$lineItem['price_field_id']] = $lineItem; } } return $lineSets; } + /** + * Returns array with statuses that are considered to make a recurring contribution inactive. + * + * @return array + */ + public static function getInactiveStatuses() { + return ['Cancelled', 'Failed', 'Completed']; + } + + /** + * Get options for the called BAO object's field. + * + * This function can be overridden by each BAO to add more logic related to context. + * The overriding function will generally call the lower-level CRM_Core_PseudoConstant::get + * + * @param string $fieldName + * @param string $context + * @see CRM_Core_DAO::buildOptionsContext + * @param array $props + * whatever is known about this bao object. + * + * @return array|bool + */ + public static function buildOptions($fieldName, $context = NULL, $props = []) { + + switch ($fieldName) { + case 'payment_processor_id': + if (isset(\Civi::$statics[__CLASS__]['buildoptions_payment_processor_id'])) { + return \Civi::$statics[__CLASS__]['buildoptions_payment_processor_id']; + } + $baoName = 'CRM_Contribute_BAO_ContributionRecur'; + $props['condition']['test'] = "is_test = 0"; + $liveProcessors = CRM_Core_PseudoConstant::get($baoName, $fieldName, $props, $context); + $props['condition']['test'] = "is_test != 0"; + $testProcessors = CRM_Core_PseudoConstant::get($baoName, $fieldName, $props, $context); + foreach ($testProcessors as $key => $value) { + if ($context === 'validate') { + // @fixme: Ideally the names would be different in the civicrm_payment_processor table but they are not. + // So we append '_test' to the test one so that we can select the correct processor by name using the ContributionRecur.create API. + $testProcessors[$key] = $value . '_test'; + } + else { + $testProcessors[$key] = CRM_Core_TestEntity::appendTestText($value); + } + } + $allProcessors = $liveProcessors + $testProcessors; + ksort($allProcessors); + \Civi::$statics[__CLASS__]['buildoptions_payment_processor_id'] = $allProcessors; + return $allProcessors; + } + return parent::buildOptions($fieldName, $context, $props); + } + } diff --git a/CRM/Contribute/BAO/ContributionSoft.php b/CRM/Contribute/BAO/ContributionSoft.php index 27fd130ae0e9..e219d687dce3 100644 --- a/CRM/Contribute/BAO/ContributionSoft.php +++ b/CRM/Contribute/BAO/ContributionSoft.php @@ -1,9 +1,9 @@ pcp_id && empty($pcpId)) { @@ -149,7 +149,7 @@ public static function formatSoftCreditParams(&$params, &$form) { $honorId = NULL; // @todo fix use of deprecated function. - $contributionSoftParams['soft_credit_type_id'] = CRM_Core_OptionGroup::getValue('soft_credit_type', 'pcp', 'name'); + $contributionSoftParams['soft_credit_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', 'pcp'); //check if there is any duplicate contact // honoree should never be the donor $exceptKeys = array( @@ -168,8 +168,9 @@ public static function formatSoftCreditParams(&$params, &$form) { $honorId = CRM_Utils_Array::value(0, $ids); } + $null = []; $honorId = CRM_Contact_BAO_Contact::createProfileContact( - $params['honor'], CRM_Core_DAO::$_nullArray, + $params['honor'], $null, $honorId, NULL, $form->_values['honoree_profile_id'] ); @@ -253,15 +254,14 @@ public static function getSoftContributionTotals($contact_id, $isTest = 0) { $cs = CRM_Core_DAO::executeQuery($query, $params); - $count = 0; + $count = $countCancelled = 0; $amount = $average = $cancelAmount = array(); while ($cs->fetch()) { if ($cs->amount > 0) { $count++; - $amount[] = $cs->amount; - $average[] = $cs->average; - $currency[] = $cs->currency; + $amount[] = CRM_Utils_Money::format($cs->amount, $cs->currency); + $average[] = CRM_Utils_Money::format($cs->average, $cs->currency); } } @@ -271,16 +271,17 @@ public static function getSoftContributionTotals($contact_id, $isTest = 0) { $cancelAmountSQL = CRM_Core_DAO::executeQuery($query, $params); while ($cancelAmountSQL->fetch()) { if ($cancelAmountSQL->amount > 0) { - $count++; - $cancelAmount[] = $cancelAmountSQL->amount; + $countCancelled++; + $cancelAmount[] = CRM_Utils_Money::format($cancelAmountSQL->amount, $cancelAmountSQL->currency); } } - if ($count > 0) { + if ($count > 0 || $countCancelled > 0) { return array( + $count, + $countCancelled, implode(', ', $amount), implode(', ', $average), - implode(', ', $currency), implode(', ', $cancelAmount), ); } @@ -298,39 +299,53 @@ public static function getSoftContributionTotals($contact_id, $isTest = 0) { * Array of soft contribution ids, amounts, and associated contact ids */ public static function getSoftContribution($contributionID, $all = FALSE) { - $pcpFields = array( + $softContributionFields = self::getSoftCreditContributionFields([$contributionID], $all); + return isset($softContributionFields[$contributionID]) ? $softContributionFields[$contributionID] : []; + } + + /** + * Retrieve soft contributions for an array of contribution records. + * + * @param array $contributionIDs + * @param bool $all + * Include PCP data. + * + * @return array + * Array of soft contribution ids, amounts, and associated contact ids + */ + public static function getSoftCreditContributionFields($contributionIDs, $all = FALSE) { + $pcpFields = [ 'pcp_id', 'pcp_title', 'pcp_display_in_roll', 'pcp_roll_nickname', 'pcp_personal_note', - ); + ]; - $query = ' - SELECT ccs.id, pcp_id, cpcp.title as pcp_title, pcp_display_in_roll, pcp_roll_nickname, pcp_personal_note, ccs.currency as currency, amount, ccs.contact_id as contact_id, c.display_name, ccs.soft_credit_type_id + $query = " + SELECT ccs.id, pcp_id, ccs.contribution_id as contribution_id, cpcp.title as pcp_title, pcp_display_in_roll, pcp_roll_nickname, pcp_personal_note, ccs.currency as currency, amount, ccs.contact_id as contact_id, c.display_name, ccs.soft_credit_type_id FROM civicrm_contribution_soft ccs INNER JOIN civicrm_contact c on c.id = ccs.contact_id LEFT JOIN civicrm_pcp cpcp ON ccs.pcp_id = cpcp.id - WHERE contribution_id = %1; - '; - - $params = array(1 => array($contributionID, 'Integer')); - - $dao = CRM_Core_DAO::executeQuery($query, $params); + WHERE contribution_id IN (%1) + "; + $queryParams = [1 => [implode(',', $contributionIDs), 'CommaSeparatedIntegers']]; + $dao = CRM_Core_DAO::executeQuery($query, $queryParams); - $softContribution = array(); - $count = 1; + $softContribution = $indexes = []; while ($dao->fetch()) { if ($dao->pcp_id) { if ($all) { foreach ($pcpFields as $val) { - $softContribution[$val] = $dao->$val; + $softContribution[$dao->contribution_id][$val] = $dao->$val; } - $softContribution['pcp_soft_credit_to_name'] = $dao->display_name; - $softContribution['pcp_soft_credit_to_id'] = $dao->contact_id; + $softContribution[$dao->contribution_id]['pcp_soft_credit_to_name'] = $dao->display_name; + $softContribution[$dao->contribution_id]['pcp_soft_credit_to_id'] = $dao->contact_id; } } else { - $softContribution['soft_credit'][$count] = array( + // Use a 1-based array because that's what this function returned before refactoring in https://github.com/civicrm/civicrm-core/pull/14747 + $indexes[$dao->contribution_id] = isset($indexes[$dao->contribution_id]) ? $indexes[$dao->contribution_id] + 1 : 1; + $softContribution[$dao->contribution_id]['soft_credit'][$indexes[$dao->contribution_id]] = [ 'contact_id' => $dao->contact_id, 'soft_credit_id' => $dao->id, 'currency' => $dao->currency, @@ -338,8 +353,7 @@ public static function getSoftContribution($contributionID, $all = FALSE) { 'contact_name' => $dao->display_name, 'soft_credit_type' => $dao->soft_credit_type_id, 'soft_credit_type_label' => CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', $dao->soft_credit_type_id), - ); - $count++; + ]; } } @@ -520,7 +534,7 @@ public static function getSoftContributionList($contact_id, $filter = NULL, $isT $result[$cs->id]['links'] = CRM_Core_Action::formLink($links, NULL, $replace); if ($isTest) { - $result[$cs->id]['contribution_status'] = $result[$cs->id]['contribution_status'] . '
    (test)'; + $result[$cs->id]['contribution_status'] = CRM_Core_TestEntity::appendTestText($result[$cs->id]['contribution_status']); } } return $result; diff --git a/CRM/Contribute/BAO/ManagePremiums.php b/CRM/Contribute/BAO/ManagePremiums.php index 9ddf411b79f3..97d1f632e93d 100644 --- a/CRM/Contribute/BAO/ManagePremiums.php +++ b/CRM/Contribute/BAO/ManagePremiums.php @@ -1,9 +1,9 @@ copyValues($params); - if ($premium->find(TRUE)) { - $premium->product_name = $premium->name; - CRM_Core_DAO::storeValues($premium, $defaults); - return $premium; - } - return NULL; + CRM_Core_Error::deprecatedFunctionWarning('CRM_Contribute_BAO_Product::retrieve'); + return parent::retrieve($params, $defaults); } /** * Update the is_active flag in the db. * + * @deprecated * @param int $id * Id of the database record. * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool */ public static function setIsActive($id, $is_active) { - if (!$is_active) { - $dao = new CRM_Contribute_DAO_PremiumsProduct(); - $dao->product_id = $id; - $dao->delete(); - } - return CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Product', $id, 'is_active', $is_active); + CRM_Core_Error::deprecatedFunctionWarning('CRM_Contribute_BAO_Product::setIsActive'); + return parent::setIsActive($id, $is_active); } /** - * add the financial types. + * Add a premium product to the database, and return it. * + * @deprecated * @param array $params * Reference array contains the values submitted by the form. - * @param array $ids + * @param array $ids (deprecated) * Reference array contains the id. * - * - * @return object + * @return CRM_Contribute_DAO_Product */ - public static function add(&$params, &$ids) { - // CRM-14283 - strip protocol and domain from image URLs - $image_type = array('image', 'thumbnail'); - foreach ($image_type as $key) { - if (isset($params[$key]) && $params[$key]) { - $parsedURL = explode('/', $params[$key]); - $pathComponents = array_slice($parsedURL, 3); - $params[$key] = '/' . implode('/', $pathComponents); - } + public static function add(&$params, $ids) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Contribute_BAO_Product::create'); + $id = CRM_Utils_Array::value('id', $params, CRM_Utils_Array::value('premium', $ids)); + if ($id) { + $params['id'] = $id; } - - $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE); - $params['is_deductible'] = CRM_Utils_Array::value('is_deductible', $params, FALSE); - - // action is taken depending upon the mode - $premium = new CRM_Contribute_DAO_Product(); - $premium->copyValues($params); - - $premium->id = CRM_Utils_Array::value('premium', $ids); - - // set currency for CRM-1496 - if (!isset($premium->currency)) { - $config = CRM_Core_Config::singleton(); - $premium->currency = $config->defaultCurrency; - } - - $premium->save(); - return $premium; + return parent::create($params); } /** * Delete premium Types. * + * @deprecated * @param int $productID + * + * @throws \CRM_Core_Exception */ public static function del($productID) { - //check dependencies - $premiumsProduct = new CRM_Contribute_DAO_PremiumsProduct(); - $premiumsProduct->product_id = $productID; - if ($premiumsProduct->find(TRUE)) { - $session = CRM_Core_Session::singleton(); - $message .= ts('This Premium is being linked to Online Contribution page. Please remove it in order to delete this Premium.', array(1 => CRM_Utils_System::url('civicrm/admin/contribute', 'reset=1')), ts('Deletion Error'), 'error'); - CRM_Core_Session::setStatus($message); - return CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/contribute/managePremiums', 'reset=1&action=browse')); - } - - //delete from financial Type table - $premium = new CRM_Contribute_DAO_Product(); - $premium->id = $productID; - $premium->delete(); + CRM_Core_Error::deprecatedFunctionWarning('CRM_Contribute_BAO_Product::del'); + return parent::del($productID); } } diff --git a/CRM/Contribute/BAO/Premium.php b/CRM/Contribute/BAO/Premium.php index 8984e0558a7c..f3fca06da77a 100644 --- a/CRM/Contribute/BAO/Premium.php +++ b/CRM/Contribute/BAO/Premium.php @@ -1,9 +1,9 @@ add('hidden', "selectProduct", $selectedProductID, array('id' => 'selectProduct')); + $form->add('hidden', "selectProduct", $selectedProductID, ['id' => 'selectProduct']); - $dao = new CRM_Contribute_DAO_Premium(); - $dao->entity_table = 'civicrm_contribution_page'; - $dao->entity_id = $pageID; - $dao->premiums_active = 1; - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::ADD); - $addWhere = "financial_type_id IN (0)"; - if (!empty($financialTypes)) { - $addWhere = "financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; - } + $premiumDao = new CRM_Contribute_DAO_Premium(); + $premiumDao->entity_table = 'civicrm_contribution_page'; + $premiumDao->entity_id = $pageID; + $premiumDao->premiums_active = 1; - if ($dao->find(TRUE)) { - $premiumID = $dao->id; - $premiumBlock = array(); - CRM_Core_DAO::storeValues($dao, $premiumBlock); + if ($premiumDao->find(TRUE)) { + $premiumID = $premiumDao->id; + $premiumBlock = []; + CRM_Core_DAO::storeValues($premiumDao, $premiumBlock); - $dao = new CRM_Contribute_DAO_PremiumsProduct(); - $dao->premiums_id = $premiumID; - $dao->whereAdd($addWhere); - $dao->orderBy('weight'); - $dao->find(); + CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::ADD); + $addWhere = "financial_type_id IN (0)"; + if (!empty($financialTypes)) { + $addWhere = "financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")"; + } + $addWhere = "{$addWhere} OR financial_type_id IS NULL"; - $products = array(); - $radio = array(); - while ($dao->fetch()) { + $premiumsProductDao = new CRM_Contribute_DAO_PremiumsProduct(); + $premiumsProductDao->premiums_id = $premiumID; + $premiumsProductDao->whereAdd($addWhere); + $premiumsProductDao->orderBy('weight'); + $premiumsProductDao->find(); + + $products = []; + while ($premiumsProductDao->fetch()) { $productDAO = new CRM_Contribute_DAO_Product(); - $productDAO->id = $dao->product_id; + $productDAO->id = $premiumsProductDao->product_id; $productDAO->is_active = 1; if ($productDAO->find(TRUE)) { if ($selectedProductID != NULL) { @@ -147,7 +148,7 @@ public static function buildPremiumBlock(&$form, $pageID, $formItems = FALSE, $s CRM_Core_DAO::storeValues($productDAO, $products[$productDAO->id]); } } - $options = $temp = array(); + $options = $temp = []; $temp = explode(',', $productDAO->options); foreach ($temp as $value) { $options[trim($value)] = trim($value); @@ -187,7 +188,7 @@ public function buildPremiumPreviewBlock($form, $productID, $premiumProductID = } $radio[$productDAO->id] = $form->createElement('radio', NULL, NULL, NULL, $productDAO->id, NULL); - $options = $temp = array(); + $options = $temp = []; $temp = explode(',', $productDAO->options); foreach ($temp as $value) { $options[$value] = $value; @@ -217,10 +218,10 @@ public static function deletePremium($contributionPageID) { //need to delete entries from civicrm_premiums //as well as from civicrm_premiums_product, CRM-4586 - $params = array( + $params = [ 'entity_id' => $contributionPageID, 'entity_table' => 'civicrm_contribution_page', - ); + ]; $premium = new CRM_Contribute_DAO_Premium(); $premium->copyValues($params); @@ -244,7 +245,7 @@ public static function deletePremium($contributionPageID) { */ public static function getPremiumProductInfo() { if (!self::$productInfo) { - $products = $options = array(); + $products = $options = []; $dao = new CRM_Contribute_DAO_Product(); $dao->is_active = 1; @@ -261,7 +262,7 @@ public static function getPremiumProductInfo() { } } - self::$productInfo = array($products, $options); + self::$productInfo = [$products, $options]; } return self::$productInfo; } diff --git a/CRM/Contribute/BAO/Product.php b/CRM/Contribute/BAO/Product.php new file mode 100644 index 000000000000..f3505f972cfc --- /dev/null +++ b/CRM/Contribute/BAO/Product.php @@ -0,0 +1,146 @@ +copyValues($params); + if ($premium->find(TRUE)) { + $premium->product_name = $premium->name; + CRM_Core_DAO::storeValues($premium, $defaults); + return $premium; + } + return NULL; + } + + /** + * Update the is_active flag in the db. + * + * @param int $id + * Id of the database record. + * @param bool $is_active + * Value we want to set the is_active field. + * + * @return bool + */ + public static function setIsActive($id, $is_active) { + if (!$is_active) { + $dao = new CRM_Contribute_DAO_PremiumsProduct(); + $dao->product_id = $id; + $dao->delete(); + } + return CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Product', $id, 'is_active', $is_active); + } + + /** + * Add a premium product to the database, and return it. + * + * @param array $params + * Update parameters. + * + * @return CRM_Contribute_DAO_Product + */ + public static function create($params) { + $id = CRM_Utils_Array::value('id', $params); + if (empty($id)) { + $defaultParams = [ + 'id' => $id, + 'image' => '', + 'thumbnail' => '', + 'is_active' => 0, + 'is_deductible' => FALSE, + 'currency' => CRM_Core_Config::singleton()->defaultCurrency, + ]; + $params = array_merge($defaultParams, $params); + } + + // Modify the submitted values for 'image' and 'thumbnail' so that we use + // local URLs for these images when possible. + if (isset($params['image'])) { + $params['image'] = CRM_Utils_String::simplifyURL($params['image'], TRUE); + } + if (isset($params['thumbnail'])) { + $params['thumbnail'] = CRM_Utils_String::simplifyURL($params['thumbnail'], TRUE); + } + + // Save and return + $premium = new CRM_Contribute_DAO_Product(); + $premium->copyValues($params); + $premium->save(); + return $premium; + } + + /** + * Delete premium Types. + * + * @param int $productID + * + * @throws \CRM_Core_Exception + */ + public static function del($productID) { + //check dependencies + $premiumsProduct = new CRM_Contribute_DAO_PremiumsProduct(); + $premiumsProduct->product_id = $productID; + if ($premiumsProduct->find(TRUE)) { + throw new CRM_Core_Exception('Cannot delete a Premium that is linked to a Contribution page'); + } + // delete product + $premium = new CRM_Contribute_DAO_Product(); + $premium->id = $productID; + $premium->delete(); + } + +} diff --git a/CRM/Contribute/BAO/Query.php b/CRM/Contribute/BAO/Query.php index 185f38cfe753..226544fc227e 100644 --- a/CRM/Contribute/BAO/Query.php +++ b/CRM/Contribute/BAO/Query.php @@ -1,9 +1,9 @@ $field) { + // We can only safely add in those with unique names as those without could clobber others. + // The array is keyed by unique names so if it doesn't match the key there is no unique name & we unset + // Refer to CRM_Contribute_Form_SearchTest for existing tests ... and to add more! + if ($field['name'] === $fieldKey) { + unset($recurFields[$fieldKey]); + } + } + $fields = array_merge($recurFields, CRM_Contribute_BAO_Contribution::exportableFields($checkPermission)); + CRM_Contribute_BAO_Contribution::appendPseudoConstantsToFields($fields); unset($fields['contribution_contact_id']); \Civi::$statics[__CLASS__]['fields']['contribution'] = $fields; } @@ -68,14 +78,6 @@ public static function select(&$query) { $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; } - // get financial_type - if (!empty($query->_returnProperties['financial_type'])) { - $query->_select['financial_type'] = "civicrm_financial_type.name as financial_type"; - $query->_element['financial_type'] = 1; - $query->_tables['civicrm_contribution'] = 1; - $query->_tables['civicrm_financial_type'] = 1; - } - // get accounting code if (!empty($query->_returnProperties['accounting_code'])) { $query->_select['accounting_code'] = "civicrm_financial_account.accounting_code as accounting_code"; @@ -118,7 +120,7 @@ public static function where(&$query) { continue; } if (substr($query->_params[$id][0], 0, 13) == 'contribution_' || substr($query->_params[$id][0], 0, 10) == 'financial_' || substr($query->_params[$id][0], 0, 8) == 'payment_') { - if ($query->_mode == CRM_Contact_BAO_QUERY::MODE_CONTACTS) { + if ($query->_mode == CRM_Contact_BAO_Query::MODE_CONTACTS) { $query->_useDistinct = TRUE; } // CRM-12065 @@ -144,6 +146,9 @@ public static function where(&$query) { * * @param array $values * @param CRM_Contact_BAO_Query $query + * + * @throws \CiviCRM_API3_Exception + * @throws \CRM_Core_Exception */ public static function whereClauseSingle(&$values, &$query) { list($name, $op, $value, $grouping, $wildcard) = $values; @@ -155,28 +160,25 @@ public static function whereClauseSingle(&$values, &$query) { $quoteValue = "\"$value\""; } - $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'; + $fieldAliases = self::getLegacySupportedFields(); - $recurrringFields = CRM_Contribute_BAO_ContributionRecur::getRecurringFields(); - unset($recurrringFields['contribution_recur_payment_made']); - foreach ($recurrringFields as $dateField => $dateFieldTitle) { - if (self::buildDateWhere($values, $query, $name, $dateField, $dateFieldTitle)) { - return; - } - } - // These are legacy names. - // @todo enotices when these are hit so we can start to elimnate them. - $fieldAliases = array( - 'financial_type' => 'financial_type_id', - 'contribution_page' => 'contribution_page_id', - 'payment_instrument' => 'payment_instrument_id', - // or payment_instrument_id? - 'contribution_payment_instrument' => 'contribution_payment_instrument_id', - 'contribution_status' => 'contribution_status_id', - ); - $name = isset($fieldAliases[$name]) ? $fieldAliases[$name] : $name; + $fieldName = $name = self::getFieldName($values); $qillName = $name; - $pseudoExtraParam = array(); + if (in_array($name, $fieldAliases)) { + $qillName = array_search($name, $fieldAliases); + } + $pseudoExtraParam = []; + $fieldSpec = CRM_Utils_Array::value($fieldName, $fields, []); + $tableName = CRM_Utils_Array::value('table_name', $fieldSpec, 'civicrm_contribution'); + $dataType = CRM_Utils_Type::typeToString(CRM_Utils_Array::value('type', $fieldSpec)); + if ($dataType === 'Timestamp' || $dataType === 'Date') { + $title = empty($fieldSpec['unique_title']) ? $fieldSpec['title'] : $fieldSpec['unique_title']; + $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; + $query->dateQueryBuilder($values, + $tableName, $fieldName, $fieldSpec['name'], $title + ); + return; + } switch ($name) { case 'contribution_date': @@ -184,9 +186,10 @@ public static function whereClauseSingle(&$values, &$query) { case 'contribution_date_low_time': case 'contribution_date_high': case 'contribution_date_high_time': + CRM_Core_Error::deprecatedFunctionWarning('search by receive_date'); // process to / from date $query->dateQueryBuilder($values, - 'civicrm_contribution', 'contribution_date', 'receive_date', 'Contribution Date' + 'civicrm_contribution', 'contribution_date', 'receive_date', ts('Contribution Date') ); return; @@ -228,10 +231,8 @@ public static function whereClauseSingle(&$values, &$query) { return; case 'financial_type_id': - // @todo we need to make this resemble a hook approach. - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution.$name", 'IN', array_keys($financialTypes), 'String'); case 'invoice_id': + case 'invoice_number': case 'payment_instrument_id': case 'contribution_payment_instrument_id': case 'contribution_page_id': @@ -244,14 +245,13 @@ public static function whereClauseSingle(&$values, &$query) { case 'contribution_check_number': case 'contribution_contact_id': case (strpos($name, '_amount') !== FALSE): - case (strpos($name, '_date') !== FALSE && $name != 'contribution_fulfilled_date'): case 'contribution_campaign_id': - $fieldNamesNotToStripContributionFrom = array( + $fieldNamesNotToStripContributionFrom = [ 'contribution_currency_type', 'contribution_status_id', 'contribution_page_id', - ); + ]; // @todo these are mostly legacy params. Find a better way to deal with them. if (!in_array($name, $fieldNamesNotToStripContributionFrom) ) { @@ -260,16 +260,18 @@ public static function whereClauseSingle(&$values, &$query) { } $name = str_replace('contribution_', '', $name); } - if (in_array($name, array('contribution_currency', 'contribution_currency_type'))) { + if (in_array($name, ['contribution_currency', 'contribution_currency_type'])) { $qillName = $name = 'currency'; - $pseudoExtraParam = array('labelColumn' => 'name'); + $pseudoExtraParam = ['labelColumn' => 'name']; } $dataType = !empty($fields[$qillName]['type']) ? CRM_Utils_Type::typeToString($fields[$qillName]['type']) : 'String'; $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution.$name", $op, $value, $dataType); list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contribute_DAO_Contribution', $name, $value, $op, $pseudoExtraParam); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$qillName]['title'], 2 => $op, 3 => $value)); + if (!($name == 'id' && $value == 0)) { + $query->_qill[$grouping][] = ts('%1 %2 %3', [1 => $fields[$qillName]['title'], 2 => $op, 3 => $value]); + } $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; return; @@ -278,7 +280,7 @@ public static function whereClauseSingle(&$values, &$query) { $qillName = $name; if ($name == 'contribution_pcp_made_through_id') { $qillName = $name = 'pcp_id'; - $fields[$name] = array('title' => ts('Personal Campaign Page'), 'type' => 2); + $fields[$name] = ['title' => ts('Personal Campaign Page'), 'type' => 2]; } if ($name == 'contribution_soft_credit_type_id') { $qillName = str_replace('_id', '', $qillName); @@ -289,7 +291,7 @@ public static function whereClauseSingle(&$values, &$query) { $op, $value, CRM_Utils_Type::typeToString($fields[$qillName]['type']) ); list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contribute_DAO_ContributionSoft', $name, $value, $op); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$qillName]['title'], 2 => $op, 3 => $value)); + $query->_qill[$grouping][] = ts('%1 %2 %3', [1 => $fields[$qillName]['title'], 2 => $op, 3 => $value]); $query->_tables['civicrm_contribution_soft'] = $query->_whereTables['civicrm_contribution_soft'] = 1; return; @@ -363,13 +365,25 @@ public static function whereClauseSingle(&$values, &$query) { $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; return; + case 'contribution_recur_payment_processor_id': + $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution_recur.payment_processor_id", $op, $value, "String"); + $paymentProcessors = civicrm_api3('PaymentProcessor', 'get', []); + $paymentProcessorNames = []; + foreach ($value as $paymentProcessorId) { + $paymentProcessorNames[] = $paymentProcessors['values'][$paymentProcessorId]['name']; + } + $query->_qill[$grouping][] = ts("Recurring Contribution Payment Processor %1 %2", [1 => $op, 2 => implode(', ', $paymentProcessorNames)]); + $query->_tables['civicrm_contribution_recur'] = $query->_whereTables['civicrm_contribution_recur'] = 1; + return; + case 'contribution_recur_processor_id': case 'contribution_recur_trxn_id': - $fieldName = str_replace('contribution_recur_', '', $name); - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution_recur.{$fieldName}", + $spec = $fields[$name]; + $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($spec['where'], $op, $value, "String" ); - $query->_tables['civicrm_contribution_recur'] = $query->_whereTables['civicrm_contribution_recur'] = 1; + $query->_qill[$grouping][] = ts("Recurring Contribution %1 %2 '%3'", [1 => $fields[$name]['title'], 2 => $op, 3 => $value]); + $query->_tables[$spec['table_name']] = $query->_whereTables[$spec['table_name']] = 1; return; case 'contribution_recur_payment_made': @@ -385,16 +399,21 @@ public static function whereClauseSingle(&$values, &$query) { $query->_tables['civicrm_contribution_recur'] = $query->_whereTables['civicrm_contribution_recur'] = 1; return; + case 'contribution_recur_contribution_status_id': + $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution_recur.contribution_status_id", $op, $value, 'String'); + list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contribute_DAO_ContributionRecur', 'contribution_status_id', $value, $op, $pseudoExtraParam); + $query->_qill[$grouping][] = ts("Recurring Contribution Status %1 '%2'", [1 => $op, 2 => $value]); + $query->_tables['civicrm_contribution_recur'] = $query->_whereTables['civicrm_contribution_recur'] = 1; + return; + case 'contribution_note': - $value = $strtolower(CRM_Core_DAO::escapeString($value)); + $value = CRM_Core_DAO::escapeString($value); if ($wildcard) { $value = "%$value%"; $op = 'LIKE'; } - // LOWER roughly translates to 'hurt my database without deriving any benefit' See CRM-19811. - $wc = ($op != 'LIKE') ? "LOWER(civicrm_note.note)" : "civicrm_note.note"; - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($wc, $op, $value, "String"); - $query->_qill[$grouping][] = ts('Contribution Note %1 %2', array(1 => $op, 2 => $quoteValue)); + $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_note.note', $op, $value, "String"); + $query->_qill[$grouping][] = ts('Contribution Note %1 %2', [1 => $op, 2 => $quoteValue]); $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = $query->_whereTables['contribution_note'] = 1; return; @@ -422,7 +441,7 @@ public static function whereClauseSingle(&$values, &$query) { case 'contribution_batch_id': list($qillOp, $qillValue) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Batch_BAO_EntityBatch', 'batch_id', $value, $op); - $query->_qill[$grouping][] = ts('Batch Name %1 %2', array(1 => $qillOp, 2 => $qillValue)); + $query->_qill[$grouping][] = ts('Batch Name %1 %2', [1 => $qillOp, 2 => $qillValue]); $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause('civicrm_entity_batch.batch_id', $op, $value); $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; $query->_tables['civicrm_financial_trxn'] = $query->_whereTables['civicrm_financial_trxn'] = 1; @@ -433,7 +452,7 @@ public static function whereClauseSingle(&$values, &$query) { // CRM-16713 - contribution search by premiums on 'Find Contribution' form. $qillName = $name; list($operator, $productValue) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Contribute_DAO_Product', $name, $value, $op); - $query->_qill[$grouping][] = ts('%1 %2 %3', array(1 => $fields[$qillName]['title'], 2 => $operator, 3 => $productValue)); + $query->_qill[$grouping][] = ts('%1 %2 %3', [1 => $fields[$qillName]['title'], 2 => $operator, 3 => $productValue]); $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_product.id", $op, $value); $query->_tables['civicrm_product'] = $query->_whereTables['civicrm_product'] = 1; return; @@ -448,7 +467,7 @@ public static function whereClauseSingle(&$values, &$query) { $query->_tables['civicrm_financial_trxn'] = $query->_whereTables['civicrm_financial_trxn'] = 1; $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Financial_DAO_FinancialTrxn', 'card_type_id', $value, $op); - $query->_qill[$grouping][] = ts('Card Type %1 %2', array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts('Card Type %1 %2', [1 => $op, 2 => $value]); return; case 'financial_trxn_pan_truncation': @@ -456,25 +475,25 @@ public static function whereClauseSingle(&$values, &$query) { $query->_tables['civicrm_financial_trxn'] = $query->_whereTables['civicrm_financial_trxn'] = 1; $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; list($op, $value) = CRM_Contact_BAO_Query::buildQillForFieldValue('CRM_Financial_DAO_FinancialTrxn', 'pan_truncation', $value, $op); - $query->_qill[$grouping][] = ts('Card Number %1 %2', array(1 => $op, 2 => $value)); + $query->_qill[$grouping][] = ts('Card Number %1 %2', [1 => $op, 2 => $value]); return; default: //all other elements are handle in this case $fldName = substr($name, 13); - CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes); - $query->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause("civicrm_contribution.financial_type_id", 'IN', array_keys($financialTypes), 'String'); if (!isset($fields[$fldName])) { // CRM-12597 CRM_Core_Session::setStatus(ts( 'We did not recognize the search field: %1. Please check and fix your contribution related smart groups.', - array(1 => $fldName) + [1 => $fldName] ) ); return; } $whereTable = $fields[$fldName]; - $value = trim($value); + if (!is_array($value)) { + $value = trim($value); + } $dataType = "String"; if (!empty($whereTable['type'])) { @@ -485,7 +504,7 @@ public static function whereClauseSingle(&$values, &$query) { $query->_qill[$grouping][] = "$whereTable[title] $op $quoteValue"; list($tableName) = explode('.', $whereTable['where'], 2); $query->_tables[$tableName] = $query->_whereTables[$tableName] = 1; - if ($tableName == 'civicrm_contribution_product') { + if ($tableName === 'civicrm_contribution_product') { $query->_tables['civicrm_product'] = $query->_whereTables['civicrm_product'] = 1; $query->_tables['civicrm_contribution'] = $query->_whereTables['civicrm_contribution'] = 1; } @@ -509,9 +528,9 @@ public static function from($name, $mode, $side) { switch ($name) { case 'civicrm_contribution': $from = " $side JOIN civicrm_contribution ON civicrm_contribution.contact_id = contact_a.id "; - if (in_array(self::$_contribOrSoftCredit, array("only_scredits", "both_related", "both"))) { + if (in_array(self::$_contribOrSoftCredit, ["only_scredits", "both_related", "both"])) { // switch the from table if its only soft credit search - $from = " $side JOIN contribution_search_scredit_combined ON contribution_search_scredit_combined.contact_id = contact_a.id "; + $from = " $side JOIN " . \Civi::$statics[__CLASS__]['soft_credit_temp_table_name'] . " as contribution_search_scredit_combined ON contribution_search_scredit_combined.contact_id = contact_a.id "; $from .= " $side JOIN civicrm_contribution ON civicrm_contribution.id = contribution_search_scredit_combined.id "; $from .= " $side JOIN civicrm_contribution_soft ON civicrm_contribution_soft.id = contribution_search_scredit_combined.scredit_id"; } @@ -594,13 +613,13 @@ public static function from($name, $mode, $side) { break; case 'civicrm_contribution_soft': - if (!in_array(self::$_contribOrSoftCredit, array("only_scredits", "both_related", "both"))) { + if (!in_array(self::$_contribOrSoftCredit, ["only_scredits", "both_related", "both"])) { $from = " $side JOIN civicrm_contribution_soft ON civicrm_contribution_soft.contribution_id = civicrm_contribution.id"; } break; case 'civicrm_contribution_soft_contact': - if (in_array(self::$_contribOrSoftCredit, array("only_scredits", "both_related", "both"))) { + if (in_array(self::$_contribOrSoftCredit, ["only_scredits", "both_related", "both"])) { $from .= " $side JOIN civicrm_contact civicrm_contact_d ON (civicrm_contribution.contact_id = civicrm_contact_d.id ) AND contribution_search_scredit_combined.scredit_id IS NOT NULL"; } @@ -663,8 +682,7 @@ public static function initializeAnySoftCreditClause(&$query) { * * @return bool */ - public static function isSoftCreditOptionEnabled($queryParams = array()) { - static $tempTableFilled = FALSE; + public static function isSoftCreditOptionEnabled($queryParams = []) { if (!empty($queryParams)) { foreach (array_keys($queryParams) as $id) { if (empty($queryParams[$id][0])) { @@ -676,21 +694,21 @@ public static function isSoftCreditOptionEnabled($queryParams = array()) { } } if (in_array(self::$_contribOrSoftCredit, - array("only_scredits", "both_related", "both"))) { - if (!$tempTableFilled) { + ["only_scredits", "both_related", "both"])) { + if (!isset(\Civi::$statics[__CLASS__]['soft_credit_temp_table_name'])) { // build a temp table which is union of contributions and soft credits // note: group-by in first part ensures uniqueness in counts - $tempQuery = " - CREATE TEMPORARY TABLE IF NOT EXISTS contribution_search_scredit_combined AS + $tempQuery = ' SELECT con.id as id, con.contact_id, cso.id as filter_id, NULL as scredit_id FROM civicrm_contribution con LEFT JOIN civicrm_contribution_soft cso ON con.id = cso.contribution_id GROUP BY id, contact_id, scredit_id, cso.id UNION ALL SELECT scredit.contribution_id as id, scredit.contact_id, scredit.id as filter_id, scredit.id as scredit_id - FROM civicrm_contribution_soft as scredit"; - CRM_Core_DAO::executeQuery($tempQuery); - $tempTableFilled = TRUE; + FROM civicrm_contribution_soft as scredit'; + \Civi::$statics[__CLASS__]['soft_credit_temp_table_name'] = CRM_Utils_SQL_TempTable::build()->createWithQuery( + $tempQuery + )->getName(); } return TRUE; } @@ -705,11 +723,11 @@ public static function isSoftCreditOptionEnabled($queryParams = array()) { * @return array */ public static function softCreditReturnProperties($isExportMode = FALSE) { - $properties = array( + $properties = [ 'contribution_soft_credit_name' => 1, 'contribution_soft_credit_amount' => 1, 'contribution_soft_credit_type' => 1, - ); + ]; if ($isExportMode) { $properties['contribution_soft_credit_contact_id'] = 1; $properties['contribution_soft_credit_contribution_id'] = 1; @@ -722,9 +740,12 @@ public static function softCreditReturnProperties($isExportMode = FALSE) { * * The default return properties array returns far too many fields for 'everyday use. Every field you add to this array * kills a small kitten so add carefully. + * + * @param array $queryParams + * @return array */ - public static function selectorReturnProperties() { - $properties = array( + public static function selectorReturnProperties($queryParams) { + $properties = [ 'contact_type' => 1, 'contact_sub_type' => 1, 'sort_name' => 1, @@ -740,23 +761,33 @@ public static function selectorReturnProperties() { 'contribution_status_id' => 1, // @todo return this & fix query to do pseudoconstant thing. 'contribution_status' => 1, - // @todo the product field got added because it suited someone's use case. - // ideally we would have some configurability here because I think 90% of sites would - // disagree this is the right field to show - but they wouldn't agree with each other - // on what field to show instead. - 'contribution_product_id' => 1, - 'product_name' => 1, 'currency' => 1, 'cancel_date' => 1, 'contribution_recur_id' => 1, - ); - if (self::isSoftCreditOptionEnabled()) { + ]; + if (self::isSiteHasProducts()) { + $properties['product_name'] = 1; + $properties['contribution_product_id'] = 1; + } + if (self::isSoftCreditOptionEnabled($queryParams)) { $properties = array_merge($properties, self::softCreditReturnProperties()); } return $properties; } + /** + * Do any products exist in this site's database. + * + * @return bool + */ + public static function isSiteHasProducts() { + if (!isset(\Civi::$statics[__CLASS__]['has_products'])) { + \Civi::$statics[__CLASS__]['has_products'] = (bool) CRM_Core_DAO::singleValueQuery('SELECT id FROM civicrm_contribution_product LIMIT 1'); + } + return \Civi::$statics[__CLASS__]['has_products']; + } + /** * Function you should avoid. * @@ -774,7 +805,7 @@ public static function selectorReturnProperties() { public static function defaultReturnProperties($mode, $includeCustomFields = TRUE) { $properties = NULL; if ($mode & CRM_Contact_BAO_Query::MODE_CONTRIBUTE) { - $properties = array( + $properties = [ // add 'contact_type' => 1, // fields @@ -792,7 +823,7 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU // site 'thankyou_date' => 1, // performance - 'cancel_date' => 1, + 'contribution_cancel_date' => 1, // and 'total_amount' => 1, // torture @@ -813,6 +844,7 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU 'trxn_id' => 1, // join 'invoice_id' => 1, + 'invoice_number' => 1, // added 'currency' => 1, // to @@ -820,17 +852,11 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU //every 'receipt_date' => 1, // query - 'product_name' => 1, //whether - 'sku' => 1, // or - 'product_option' => 1, // not - 'fulfilled_date' => 1, // the - 'contribution_start_date' => 1, // field - 'contribution_end_date' => 1, // is 'is_test' => 1, // actually @@ -852,9 +878,17 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU // on 'contribution_campaign_id' => 1, // calling - 'contribution_product_id' => 1, //function - ); + ]; + if (self::isSiteHasProducts()) { + $properties['fulfilled_date'] = 1; + $properties['product_name'] = 1; + $properties['contribution_product_id'] = 1; + $properties['product_option'] = 1; + $properties['sku'] = 1; + $properties['contribution_start_date'] = 1; + $properties['contribution_end_date'] = 1; + } if (self::isSoftCreditOptionEnabled()) { $properties = array_merge($properties, self::softCreditReturnProperties()); } @@ -872,64 +906,80 @@ public static function defaultReturnProperties($mode, $includeCustomFields = TRU } /** - * Add all the elements shared between contribute search and advnaced search. + * Get the metadata for fields to be included on the search form. * - * @param CRM_Core_Form $form + * @throws \CiviCRM_API3_Exception */ - public static function buildSearchForm(&$form) { + public static function getSearchFieldMetadata() { + $fields = [ + 'contribution_source', + 'cancel_reason', + 'invoice_number', + 'receive_date', + 'contribution_cancel_date', + ]; + $metadata = civicrm_api3('Contribution', 'getfields', [])['values']; + return array_intersect_key($metadata, array_flip($fields)); + } - // Added contribution source - $form->addElement('text', 'contribution_source', ts('Contribution Source'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Contribution', 'source')); + /** + * Add all the elements shared between contribute search and advanced search. + * + * @param \CRM_Contribute_Form_Search $form + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + public static function buildSearchForm(&$form) { - CRM_Core_Form_Date::buildDateRange($form, 'contribution_date', 1, '_low', '_high', ts('From:'), FALSE); - // CRM-17602 - // This hidden element added for displaying Date Range error correctly. Definitely a dirty hack, but... it works. - $form->addElement('hidden', 'contribution_date_range_error'); - $form->addFormRule(array('CRM_Contribute_BAO_Query', 'formRule'), $form); + $form->addSearchFieldMetadata(['Contribution' => self::getSearchFieldMetadata()]); + $form->addSearchFieldMetadata(['ContributionRecur' => CRM_Contribute_BAO_ContributionRecur::getContributionRecurSearchFieldMetadata()]); + $form->addFormFieldsFromMetadata(); - $form->add('text', 'contribution_amount_low', ts('From'), array('size' => 8, 'maxlength' => 8)); - $form->addRule('contribution_amount_low', ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('9.99', ' '))), 'money'); + $form->add('text', 'contribution_amount_low', ts('From'), ['size' => 8, 'maxlength' => 8]); + $form->addRule('contribution_amount_low', ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('9.99', ' ')]), 'money'); - $form->add('text', 'contribution_amount_high', ts('To'), array('size' => 8, 'maxlength' => 8)); - $form->addRule('contribution_amount_high', ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('99.99', ' '))), 'money'); + $form->add('text', 'contribution_amount_high', ts('To'), ['size' => 8, 'maxlength' => 8]); + $form->addRule('contribution_amount_high', ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('99.99', ' ')]), 'money'); // Adding select option for curreny type -- CRM-4711 $form->add('select', 'contribution_currency_type', ts('Currency Type'), - array( + [ '' => ts('- any -'), - ) + - CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', array('labelColumn' => 'name')), - FALSE, array('class' => 'crm-select2') + ] + + CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', ['labelColumn' => 'name']), + FALSE, ['class' => 'crm-select2'] ); // CRM-13848 CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::VIEW); $form->addSelect('financial_type_id', - array('entity' => 'contribution', 'multiple' => 'multiple', 'context' => 'search', 'options' => $financialTypes) + ['entity' => 'contribution', 'multiple' => 'multiple', 'context' => 'search', 'options' => $financialTypes] ); $form->add('select', 'contribution_page_id', ts('Contribution Page'), CRM_Contribute_PseudoConstant::contributionPage(), - FALSE, array('class' => 'crm-select2', 'multiple' => 'multiple', 'placeholder' => ts('- any -')) + FALSE, ['class' => 'crm-select2', 'multiple' => 'multiple', 'placeholder' => ts('- any -')] ); - $form->addSelect('payment_instrument_id', - array('entity' => 'contribution', 'multiple' => 'multiple', 'label' => ts('Payment Method'), 'option_url' => NULL, 'placeholder' => ts('- any -')) + // use contribution_payment_instrument_id instead of payment_instrument_id + // Contribution Edit form (pop-up on contribution/Contact(display Result as Contribution) open on search form), + // then payment method change action not working properly because of same html ID present two time on one page + $form->addSelect('contribution_payment_instrument_id', + ['entity' => 'contribution', 'field' => 'payment_instrument_id', 'multiple' => 'multiple', 'label' => ts('Payment Method'), 'option_url' => NULL, 'placeholder' => ts('- any -')] ); $form->add('select', 'contribution_pcp_made_through_id', ts('Personal Campaign Page'), - CRM_Contribute_PseudoConstant::pcPage(), FALSE, array('class' => 'crm-select2', 'multiple' => 'multiple', 'placeholder' => ts('- any -'))); + CRM_Contribute_PseudoConstant::pcPage(), FALSE, ['class' => 'crm-select2', 'multiple' => 'multiple', 'placeholder' => ts('- any -')]); - $statusValues = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id'); - // Remove status values that are only used for recurring contributions or pledges (In Progress, Overdue). - unset($statusValues['5'], $statusValues['6']); + $statusValues = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id', 'search'); $form->add('select', 'contribution_status_id', ts('Contribution Status'), $statusValues, - FALSE, array('class' => 'crm-select2', 'multiple' => 'multiple') + FALSE, ['class' => 'crm-select2', 'multiple' => 'multiple'] ); // Add fields for thank you and receipt @@ -943,46 +993,51 @@ public static function buildSearchForm(&$form) { // Add field for transaction ID search $form->addElement('text', 'contribution_trxn_id', ts("Transaction ID")); - $form->addElement('text', 'invoice_id', ts("Invoice ID")); $form->addElement('text', 'contribution_check_number', ts('Check Number')); // Add field for pcp display in roll search $form->addYesNo('contribution_pcp_display_in_roll', ts('Personal Campaign Page Honor Roll?'), TRUE); // Soft credit related fields - $options = array( + $options = [ 'only_contribs' => ts('Contributions Only'), 'only_scredits' => ts('Soft Credits Only'), 'both_related' => ts('Soft Credits with related Hard Credit'), 'both' => ts('Both'), - ); - $form->add('select', 'contribution_or_softcredits', ts('Contributions OR Soft Credits?'), $options, FALSE, array('class' => "crm-select2")); + ]; + $form->add('select', 'contribution_or_softcredits', ts('Contributions OR Soft Credits?'), $options, FALSE, ['class' => "crm-select2"]); $form->addSelect( 'contribution_soft_credit_type_id', - array( + [ 'entity' => 'contribution_soft', 'field' => 'soft_credit_type_id', 'multiple' => TRUE, 'context' => 'search', - ) + ] ); - $form->addField('financial_trxn_card_type_id', array('entity' => 'FinancialTrxn', 'name' => 'card_type_id', 'action' => 'get')); + $form->addField('financial_trxn_card_type_id', ['entity' => 'FinancialTrxn', 'name' => 'card_type_id', 'action' => 'get']); - $form->add('text', 'financial_trxn_pan_truncation', ts('Card Number'), array( + $form->add('text', 'financial_trxn_pan_truncation', ts('Card Number'), [ 'size' => 5, 'maxlength' => 4, 'autocomplete' => 'off', - )); - - // CRM-16713 - contribution search by premiums on 'Find Contribution' form. - $form->add('select', 'contribution_product_id', - ts('Premium'), - CRM_Contribute_PseudoConstant::products(), - FALSE, array('class' => 'crm-select2', 'multiple' => 'multiple', 'placeholder' => ts('- any -')) - ); + ]); + + if (CRM_Contribute_BAO_Query::isSiteHasProducts()) { + // CRM-16713 - contribution search by premiums on 'Find Contribution' form. + $form->add('select', 'contribution_product_id', + ts('Premium'), + CRM_Contribute_PseudoConstant::products(), + FALSE, [ + 'class' => 'crm-select2', + 'multiple' => 'multiple', + 'placeholder' => ts('- any -'), + ] + ); + } - self::addCustomFormFields($form, array('Contribution')); + self::addCustomFormFields($form, ['Contribution']); CRM_Campaign_BAO_Campaign::addCampaignInComponentSearch($form, 'contribution_campaign_id'); @@ -992,17 +1047,17 @@ public static function buildSearchForm(&$form) { if (!empty($batches)) { $form->add('select', 'contribution_batch_id', ts('Batch Name'), - array( + [ '' => ts('- any -'), // CRM-19325 'IS NULL' => ts('None'), - ) + $batches, - FALSE, array('class' => 'crm-select2') + ] + $batches, + FALSE, ['class' => 'crm-select2'] ); } $form->assign('validCiviContribute', TRUE); - $form->setDefaults(array('contribution_test' => 0)); + $form->setDefaults(['contribution_test' => 0]); CRM_Contribute_BAO_ContributionRecur::recurringContribution($form); } @@ -1017,7 +1072,7 @@ public static function buildSearchForm(&$form) { public static function tableNames(&$tables) { // Add contribution table if (!empty($tables['civicrm_product'])) { - $tables = array_merge(array('civicrm_contribution' => 1), $tables); + $tables = array_merge(['civicrm_contribution' => 1], $tables); } if (!empty($tables['civicrm_contribution_product']) && empty($tables['civicrm_product'])) { @@ -1055,27 +1110,6 @@ public static function buildDateWhere(&$values, $query, $name, $field, $title) { return TRUE; } - /** - * Custom form rules. - * - * @param array $fields - * @param array $files - * @param CRM_Core_Form $form - * - * @return bool|array - */ - public static function formRule($fields, $files, $form) { - $errors = array(); - - if (empty($fields['contribution_date_high']) || empty($fields['contribution_date_low'])) { - return TRUE; - } - - CRM_Utils_Rule::validDateRange($fields, 'contribution_date', $errors, ts('Date Received')); - - return empty($errors) ? TRUE : $errors; - } - /** * Add the soft credit fields to the select fields. * diff --git a/CRM/Contribute/BAO/Widget.php b/CRM/Contribute/BAO/Widget.php index 308e4e2f95d8..396fae527388 100644 --- a/CRM/Contribute/BAO/Widget.php +++ b/CRM/Contribute/BAO/Widget.php @@ -1,9 +1,9 @@ defaultCurrencySymbol; + $data = []; + $data['currencySymbol'] = CRM_Core_BAO_Country::defaultCurrencySymbol(); if (empty($contributionPageID) || CRM_Utils_Type::validate($contributionPageID, 'Integer') == NULL @@ -91,7 +91,7 @@ public static function getContributionPageData($contributionPageID, $widgetID, $ WHERE is_test = 0 AND contribution_status_id IN ({$status}) AND contribution_page_id = %1"; - $params = array(1 => array($contributionPageID, 'Integer')); + $params = [1 => [$contributionPageID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); if ($dao->fetch()) { $data['num_donors'] = (int) $dao->count; @@ -101,11 +101,13 @@ public static function getContributionPageData($contributionPageID, $widgetID, $ $data['num_donors'] = $data['money_raised'] = $data->money_raised = 0; } + $data['money_raised_amount'] = CRM_Utils_Money::format($data['money_raised']); + $query = " SELECT goal_amount, start_date, end_date, is_active FROM civicrm_contribution_page WHERE id = %1"; - $params = array(1 => array($contributionPageID, 'Integer')); + $params = [1 => [$contributionPageID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); $data['campaign_start'] = ''; @@ -125,41 +127,40 @@ public static function getContributionPageData($contributionPageID, $widgetID, $ $now = time(); if ($dao->start_date) { $startDate = CRM_Utils_Date::unixTime($dao->start_date); - if ($startDate && - $startDate >= $now - ) { + $data['start_date'] = $dao->start_date; + if ($startDate && $startDate >= $now) { $data['is_active'] = FALSE; - $data['campaign_start'] = ts('Campaign starts on %1', array( - 1 => CRM_Utils_Date::customFormat($dao->start_date, $config->dateformatFull), - ) - ); + $data['campaign_start'] = ts('Campaign starts on %1', [ + 1 => CRM_Utils_Date::customFormat($dao->start_date, $config->dateformatFull), + ]); } } if ($dao->end_date) { $endDate = CRM_Utils_Date::unixTime($dao->end_date); + $data['end_date'] = $dao->end_date; if ($endDate && $endDate < $now ) { $data['is_active'] = FALSE; $data['campaign_start'] = ts('Campaign ended on %1', - array( + [ 1 => CRM_Utils_Date::customFormat($dao->end_date, $config->dateformatFull), - ) + ] ); } elseif ($startDate >= $now) { $data['campaign_start'] = ts('Campaign starts on %1', - array( + [ 1 => CRM_Utils_Date::customFormat($dao->start_date, $config->dateformatFull), - ) + ] ); } else { $data['campaign_start'] = ts('Campaign ends on %1', - array( + [ 1 => CRM_Utils_Date::customFormat($dao->end_date, $config->dateformatFull), - ) + ] ); } } @@ -177,13 +178,13 @@ public static function getContributionPageData($contributionPageID, $widgetID, $ $percent = $data['money_raised'] / $data['money_target']; $data['money_raised_percentage'] = (round($percent, 2)) * 100 . "%"; $data['money_target_display'] = CRM_Utils_Money::format($data['money_target']); - $data['money_raised'] = ts('Raised %1 of %2', array( - 1 => CRM_Utils_Money::format($data['money_raised']), - 2 => $data['money_target_display'], - )); + $data['money_raised'] = ts('Raised %1 of %2', [ + 1 => CRM_Utils_Money::format($data['money_raised']), + 2 => $data['money_target_display'], + ]); } else { - $data['money_raised'] = ts('Raised %1', array(1 => CRM_Utils_Money::format($data['money_raised']))); + $data['money_raised'] = ts('Raised %1', [1 => CRM_Utils_Money::format($data['money_raised'])]); } $data['money_low'] = 0; @@ -193,7 +194,7 @@ public static function getContributionPageData($contributionPageID, $widgetID, $ // if is_active is false, show this link and hide the contribute button $data['homepage_link'] = $widget->url_homepage; - $data['colors'] = array(); + $data['colors'] = []; $data['colors']["title"] = $widget->color_title; $data['colors']["button"] = $widget->color_button; diff --git a/CRM/Contribute/Controller/Contribution.php b/CRM/Contribute/Controller/Contribution.php index 082fd547a027..1dee55abcd12 100644 --- a/CRM/Contribute/Controller/Contribution.php +++ b/CRM/Contribute/Controller/Contribution.php @@ -1,9 +1,9 @@ __table = 'civicrm_contribution'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contribution_page_id', 'civicrm_contribution_page', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contribution_recur_id', 'civicrm_contribution_recur', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'address_id', 'civicrm_address', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_page_id', 'civicrm_contribution_page', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_recur_id', 'civicrm_contribution_recur', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'address_id', 'civicrm_address', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'contribution_id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'contribution_id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution ID') , - 'description' => 'Contribution ID', - 'required' => true, - 'import' => true, + 'title' => ts('Contribution ID'), + 'description' => ts('Contribution ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contribution.id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - ) , - 'contribution_contact_id' => array( + ], + 'contribution_contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to Contact ID', - 'required' => true, - 'import' => true, + 'title' => ts('Contact ID'), + 'description' => ts('FK to Contact ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contribution.contact_id', 'headerPattern' => '/contact(.?id)?/i', 'dataPattern' => '/^\d+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - 'html' => array( + 'html' => [ 'type' => 'EntityRef', - ) , - ) , - 'financial_type_id' => array( + ], + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'FK to Financial Type for (total_amount - non_deductible_amount).', - 'export' => true, + 'title' => ts('Financial Type'), + 'description' => ts('FK to Financial Type for (total_amount - non_deductible_amount).'), 'where' => 'civicrm_contribution.financial_type_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - 'contribution_page_id' => array( + ], + ], + 'contribution_page_id' => [ 'name' => 'contribution_page_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Page ID') , - 'description' => 'The Contribution Page which triggered this contribution', - 'import' => true, + 'title' => ts('Contribution Page ID'), + 'description' => ts('The Contribution Page which triggered this contribution'), + 'import' => TRUE, 'where' => 'civicrm_contribution.contribution_page_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_ContributionPage', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_contribution_page', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'payment_instrument_id' => array( + ], + ], + 'payment_instrument_id' => [ 'name' => 'payment_instrument_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Payment Method ID') , - 'description' => 'FK to Payment Instrument', - 'export' => true, + 'title' => ts('Payment Method ID'), + 'description' => ts('FK to Payment Instrument'), 'where' => 'civicrm_contribution.payment_instrument_id', 'headerPattern' => '/^payment|(p(ayment\s)?instrument)$/i', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'payment_instrument', 'optionEditPath' => 'civicrm/admin/options/payment_instrument', - ) - ) , - 'receive_date' => array( + ], + ], + 'receive_date' => [ 'name' => 'receive_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Date Received') , - 'description' => 'Date contribution was received - not necessarily the creation date of the record', - 'import' => true, + 'title' => ts('Date Received'), + 'description' => ts('Date contribution was received - not necessarily the creation date of the record'), + 'import' => TRUE, 'where' => 'civicrm_contribution.receive_date', 'headerPattern' => '/receive(.?date)?/i', 'dataPattern' => '/^\d{4}-?\d{2}-?\d{2} ?(\d{2}:?\d{2}:?(\d{2})?)?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - 'non_deductible_amount' => array( + ], + ], + 'non_deductible_amount' => [ 'name' => 'non_deductible_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Non-deductible Amount') , - 'description' => 'Portion of total amount which is NOT tax deductible. Equal to total_amount for non-deductible financial types.', - 'precision' => array( + 'title' => ts('Non-deductible Amount'), + 'description' => ts('Portion of total amount which is NOT tax deductible. Equal to total_amount for non-deductible financial types.'), + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution.non_deductible_amount', 'headerPattern' => '/non?.?deduct/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'total_amount' => array( + ], + ], + 'total_amount' => [ 'name' => 'total_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Total Amount') , - 'description' => 'Total amount of this contribution. Use market value for non-monetary gifts.', - 'required' => true, - 'precision' => array( + 'title' => ts('Total Amount'), + 'description' => ts('Total amount of this contribution. Use market value for non-monetary gifts.'), + 'required' => TRUE, + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution.total_amount', 'headerPattern' => '/^total|(.?^am(ou)?nt)/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'fee_amount' => array( + ], + ], + 'fee_amount' => [ 'name' => 'fee_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Fee Amount') , - 'description' => 'actual processor fee if known - may be 0.', - 'precision' => array( + 'title' => ts('Fee Amount'), + 'description' => ts('actual processor fee if known - may be 0.'), + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution.fee_amount', 'headerPattern' => '/fee(.?am(ou)?nt)?/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'net_amount' => array( + ], + ], + 'net_amount' => [ 'name' => 'net_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Net Amount') , - 'description' => 'actual funds transfer amount. total less fees. if processor does not report actual fee during transaction, this is set to total_amount.', - 'precision' => array( + 'title' => ts('Net Amount'), + 'description' => ts('actual funds transfer amount. total less fees. if processor does not report actual fee during transaction, this is set to total_amount.'), + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution.net_amount', 'headerPattern' => '/net(.?am(ou)?nt)?/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'trxn_id' => array( + ], + ], + 'trxn_id' => [ 'name' => 'trxn_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Transaction ID') , - 'description' => 'unique transaction id. may be processor id, bank id + trans id, or account number + check number... depending on payment_method', + 'title' => ts('Transaction ID'), + 'description' => ts('unique transaction id. may be processor id, bank id + trans id, or account number + check number... depending on payment_method'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.trxn_id', 'headerPattern' => '/tr(ansactio|x)n(.?id)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'invoice_id' => array( + ], + ], + 'invoice_id' => [ 'name' => 'invoice_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Invoice ID') , - 'description' => 'unique invoice id, system generated or passed in', + 'title' => ts('Invoice Reference'), + 'description' => ts('unique invoice id, system generated or passed in'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.invoice_id', 'headerPattern' => '/invoice(.?id)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'table_name' => 'civicrm_contribution', + 'entity' => 'Contribution', + 'bao' => 'CRM_Contribute_BAO_Contribution', + 'localizable' => 0, + 'html' => [ + 'type' => 'Text', + ], + ], + 'invoice_number' => [ + 'name' => 'invoice_number', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Invoice Number'), + 'description' => ts('Human readable invoice number'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'import' => TRUE, + 'where' => 'civicrm_contribution.invoice_number', + 'headerPattern' => '/invoice(.?number)?/i', + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'currency' => array( + ], + ], + 'currency' => [ 'name' => 'currency', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Currency') , - 'description' => '3 character string, value from config setting or input via user.', + 'title' => ts('Currency'), + 'description' => ts('3 character string, value from config setting or input via user.'), 'maxlength' => 3, 'size' => CRM_Utils_Type::FOUR, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.currency', 'headerPattern' => '/cur(rency)?/i', 'dataPattern' => '/^[A-Z]{3}$/i', - 'export' => true, + 'export' => TRUE, 'default' => 'NULL', 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_currency', 'keyColumn' => 'name', 'labelColumn' => 'full_name', 'nameColumn' => 'name', - ) - ) , - 'cancel_date' => array( + 'abbrColumn' => 'symbol', + ], + ], + 'contribution_cancel_date' => [ 'name' => 'cancel_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Cancel Date') , - 'description' => 'when was gift cancelled', - 'import' => true, + 'title' => ts('Cancelled / Refunded Date'), + 'description' => ts('when was gift cancelled'), + 'import' => TRUE, 'where' => 'civicrm_contribution.cancel_date', 'headerPattern' => '/cancel(.?date)?/i', 'dataPattern' => '/^\d{4}-?\d{2}-?\d{2} ?(\d{2}:?\d{2}:?(\d{2})?)?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - 'cancel_reason' => array( + ], + ], + 'cancel_reason' => [ 'name' => 'cancel_reason', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Cancel Reason') , - 'import' => true, + 'title' => ts('Cancellation / Refund Reason'), + 'import' => TRUE, 'where' => 'civicrm_contribution.cancel_reason', 'headerPattern' => '/(cancel.?)?reason/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'receipt_date' => array( + ], + ], + 'receipt_date' => [ 'name' => 'receipt_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Receipt Date') , - 'description' => 'when (if) receipt was sent. populated automatically for online donations w/ automatic receipting', - 'import' => true, + 'title' => ts('Receipt Date'), + 'description' => ts('when (if) receipt was sent. populated automatically for online donations w/ automatic receipting'), + 'import' => TRUE, 'where' => 'civicrm_contribution.receipt_date', 'headerPattern' => '/receipt(.?date)?/i', 'dataPattern' => '/^\d{4}-?\d{2}-?\d{2} ?(\d{2}:?\d{2}:?(\d{2})?)?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - 'thankyou_date' => array( + ], + ], + 'thankyou_date' => [ 'name' => 'thankyou_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Thank-you Date') , - 'description' => 'when (if) was donor thanked', - 'import' => true, + 'title' => ts('Thank-you Date'), + 'description' => ts('when (if) was donor thanked'), + 'import' => TRUE, 'where' => 'civicrm_contribution.thankyou_date', 'headerPattern' => '/thank(s|(.?you))?(.?date)?/i', 'dataPattern' => '/^\d{4}-?\d{2}-?\d{2} ?(\d{2}:?\d{2}:?(\d{2})?)?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - 'contribution_source' => array( + ], + ], + 'contribution_source' => [ 'name' => 'source', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contribution Source') , - 'description' => 'Origin of this Contribution.', + 'title' => ts('Contribution Source'), + 'description' => ts('Origin of this Contribution.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.source', 'headerPattern' => '/source/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'amount_level' => array( + ], + ], + 'amount_level' => [ 'name' => 'amount_level', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Amount Label') , - 'import' => true, + 'title' => ts('Amount Label'), + 'import' => TRUE, 'where' => 'civicrm_contribution.amount_level', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'contribution_recur_id' => array( + ], + ], + 'contribution_recur_id' => [ 'name' => 'contribution_recur_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Recurring Contribution ID') , - 'description' => 'Conditional foreign key to civicrm_contribution_recur id. Each contribution made in connection with a recurring contribution carries a foreign key to the recurring contribution record. This assumes we can track these processor initiated events.', - 'export' => true, + 'title' => ts('Recurring Contribution ID'), + 'description' => ts('Conditional foreign key to civicrm_contribution_recur id. Each contribution made in connection with a recurring contribution carries a foreign key to the recurring contribution record. This assumes we can track these processor initiated events.'), 'where' => 'civicrm_contribution.contribution_recur_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_ContributionRecur', - ) , - 'is_test' => array( + ], + 'is_test' => [ 'name' => 'is_test', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Test') , - 'import' => true, + 'title' => ts('Test'), + 'import' => TRUE, 'where' => 'civicrm_contribution.is_test', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'is_pay_later' => array( + ], + ], + 'is_pay_later' => [ 'name' => 'is_pay_later', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Pay Later') , - 'import' => true, + 'title' => ts('Is Pay Later'), + 'import' => TRUE, 'where' => 'civicrm_contribution.is_pay_later', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'contribution_status_id' => array( + ], + ], + 'contribution_status_id' => [ 'name' => 'contribution_status_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Status ID') , - 'import' => true, + 'title' => ts('Contribution Status ID'), + 'import' => TRUE, 'where' => 'civicrm_contribution.contribution_status_id', 'headerPattern' => '/status/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'contribution_status', 'optionEditPath' => 'civicrm/admin/options/contribution_status', - ) - ) , - 'contribution_address_id' => array( + ], + ], + 'contribution_address_id' => [ 'name' => 'address_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Address') , - 'description' => 'Conditional foreign key to civicrm_address.id. We insert an address record for each contribution when we have associated billing name and address data.', - 'export' => true, + 'title' => ts('Contribution Address'), + 'description' => ts('Conditional foreign key to civicrm_address.id. We insert an address record for each contribution when we have associated billing name and address data.'), 'where' => 'civicrm_contribution.address_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Core_DAO_Address', - ) , - 'contribution_check_number' => array( + ], + 'contribution_check_number' => [ 'name' => 'check_number', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Check Number') , + 'title' => ts('Check Number'), 'maxlength' => 255, 'size' => 6, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.check_number', 'headerPattern' => '/check(.?number)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'contribution_campaign_id' => array( + ], + ], + 'contribution_campaign_id' => [ 'name' => 'campaign_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Campaign') , - 'description' => 'The campaign for which this contribution has been triggered.', - 'import' => true, + 'title' => ts('Campaign'), + 'description' => ts('The campaign for which this contribution has been triggered.'), + 'import' => TRUE, 'where' => 'civicrm_contribution.campaign_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, 'FKClassName' => 'CRM_Campaign_DAO_Campaign', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_campaign', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'creditnote_id' => array( + ], + ], + 'creditnote_id' => [ 'name' => 'creditnote_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Credit Note ID') , - 'description' => 'unique credit note id, system generated or passed in', + 'title' => ts('Credit Note ID'), + 'description' => ts('unique credit note id, system generated or passed in'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'import' => true, + 'import' => TRUE, 'where' => 'civicrm_contribution.creditnote_id', 'headerPattern' => '/creditnote(.?id)?/i', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'tax_amount' => array( + ], + ], + 'tax_amount' => [ 'name' => 'tax_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Tax Amount') , - 'description' => 'Total tax amount of this contribution.', - 'precision' => array( + 'title' => ts('Tax Amount'), + 'description' => ts('Total tax amount of this contribution.'), + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution.tax_amount', 'headerPattern' => '/tax(.?am(ou)?nt)?/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'revenue_recognition_date' => array( + ], + ], + 'revenue_recognition_date' => [ 'name' => 'revenue_recognition_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Revenue Recognition Date') , - 'description' => 'Stores the date when revenue should be recognized.', - 'import' => true, + 'title' => ts('Revenue Recognition Date'), + 'description' => ts('Stores the date when revenue should be recognized.'), + 'import' => TRUE, 'where' => 'civicrm_contribution.revenue_recognition_date', 'headerPattern' => '/revenue(.?date)?/i', 'dataPattern' => '/^\d{4}-?\d{2}-?\d{2} ?(\d{2}:?\d{2}:?(\d{2})?)?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution', 'entity' => 'Contribution', 'bao' => 'CRM_Contribute_BAO_Contribution', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select Date', 'formatType' => 'activityDateTime', - ) , - ) , - ); + ], + ], + 'is_template' => [ + 'name' => 'is_template', + 'type' => CRM_Utils_Type::T_BOOLEAN, + 'title' => ts('Is a Template Contribution'), + 'description' => ts('Shows this is a template for recurring contributions.'), + 'import' => TRUE, + 'where' => 'civicrm_contribution.is_template', + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_contribution', + 'entity' => 'Contribution', + 'bao' => 'CRM_Contribute_BAO_Contribution', + 'localizable' => 0, + 'html' => [ + 'type' => 'CheckBox', + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -872,10 +907,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -883,91 +919,97 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_contrib_payment_instrument_id' => array( + $indices = [ + 'UI_contrib_payment_instrument_id' => [ 'name' => 'UI_contrib_payment_instrument_id', - 'field' => array( + 'field' => [ 0 => 'payment_instrument_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::payment_instrument_id', - ) , - 'index_total_amount_receive_date' => array( + ], + 'index_total_amount_receive_date' => [ 'name' => 'index_total_amount_receive_date', - 'field' => array( + 'field' => [ 0 => 'total_amount', 1 => 'receive_date', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::total_amount::receive_date', - ) , - 'index_source' => array( + ], + 'index_source' => [ 'name' => 'index_source', - 'field' => array( + 'field' => [ 0 => 'source', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::source', - ) , - 'UI_contrib_trxn_id' => array( + ], + 'UI_contrib_trxn_id' => [ 'name' => 'UI_contrib_trxn_id', - 'field' => array( + 'field' => [ 0 => 'trxn_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contribution::1::trxn_id', - ) , - 'UI_contrib_invoice_id' => array( + ], + 'UI_contrib_invoice_id' => [ 'name' => 'UI_contrib_invoice_id', - 'field' => array( + 'field' => [ 0 => 'invoice_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contribution::1::invoice_id', - ) , - 'index_contribution_status' => array( + ], + 'index_contribution_status' => [ 'name' => 'index_contribution_status', - 'field' => array( + 'field' => [ 0 => 'contribution_status_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::contribution_status_id', - ) , - 'received_date' => array( + ], + 'received_date' => [ 'name' => 'received_date', - 'field' => array( + 'field' => [ 0 => 'receive_date', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::receive_date', - ) , - 'check_number' => array( + ], + 'check_number' => [ 'name' => 'check_number', - 'field' => array( + 'field' => [ 0 => 'check_number', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::check_number', - ) , - 'index_creditnote_id' => array( + ], + 'index_creditnote_id' => [ 'name' => 'index_creditnote_id', - 'field' => array( + 'field' => [ 0 => 'creditnote_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution::0::creditnote_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/ContributionPage.php b/CRM/Contribute/DAO/ContributionPage.php index 27a9fa5c2c27..69a3cefef865 100644 --- a/CRM/Contribute/DAO/ContributionPage.php +++ b/CRM/Contribute/DAO/ContributionPage.php @@ -1,939 +1,1059 @@ __table = 'civicrm_contribution_page'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'created_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'created_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Page ID') , - 'description' => 'Contribution Id', - 'required' => true, + 'title' => ts('Contribution Page ID'), + 'description' => ts('Contribution Id'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_page.id', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'title' => array( + ], + 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contribution Page Title') , - 'description' => 'Contribution Page title. For top of page display', + 'title' => ts('Contribution Page Title'), + 'description' => ts('Contribution Page title. For top of page display'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.title', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'intro_text' => array( + ], + 'intro_text' => [ 'name' => 'intro_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Contribution Page Introduction Text') , - 'description' => 'Text and html allowed. Displayed below title.', + 'title' => ts('Contribution Page Introduction Text'), + 'description' => ts('Text and html allowed. Displayed below title.'), 'rows' => 6, 'cols' => 50, + 'where' => 'civicrm_contribution_page.intro_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'RichTextEditor', - ) , - ) , - 'financial_type_id' => array( + ], + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'default financial type assigned to contributions submitted via this page, e.g. Contribution, Campaign Contribution', + 'title' => ts('Financial Type'), + 'description' => ts('default financial type assigned to contributions submitted via this page, e.g. Contribution, Campaign Contribution'), + 'where' => 'civicrm_contribution_page.financial_type_id', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - 'payment_processor' => array( + ], + ], + 'payment_processor' => [ 'name' => 'payment_processor', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Payment Processor') , - 'description' => 'Payment Processors configured for this contribution Page', + 'title' => ts('Payment Processor'), + 'description' => ts('Payment Processors configured for this contribution Page'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.payment_processor', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_payment_processor', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - 'is_credit_card_only' => array( + ], + ], + 'is_credit_card_only' => [ 'name' => 'is_credit_card_only', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Credit Card Only?') , - 'description' => 'if true - processing logic must reject transaction at confirmation stage if pay method != credit card', + 'title' => ts('Is Credit Card Only?'), + 'description' => ts('if true - processing logic must reject transaction at confirmation stage if pay method != credit card'), + 'where' => 'civicrm_contribution_page.is_credit_card_only', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_monetary' => array( + ], + 'is_monetary' => [ 'name' => 'is_monetary', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Monetary') , - 'description' => 'if true - allows real-time monetary transactions otherwise non-monetary transactions', + 'title' => ts('Is Monetary'), + 'description' => ts('if true - allows real-time monetary transactions otherwise non-monetary transactions'), + 'where' => 'civicrm_contribution_page.is_monetary', 'default' => '1', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_recur' => array( + ], + 'is_recur' => [ 'name' => 'is_recur', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Recurring') , - 'description' => 'if true - allows recurring contributions, valid only for PayPal_Standard', + 'title' => ts('Is Recurring'), + 'description' => ts('if true - allows recurring contributions, valid only for PayPal_Standard'), + 'where' => 'civicrm_contribution_page.is_recur', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_confirm_enabled' => array( + ], + 'is_confirm_enabled' => [ 'name' => 'is_confirm_enabled', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Confirmation Page?') , - 'description' => 'if false, the confirm page in contribution pages gets skipped', + 'title' => ts('Confirmation Page?'), + 'description' => ts('if false, the confirm page in contribution pages gets skipped'), + 'where' => 'civicrm_contribution_page.is_confirm_enabled', 'default' => '1', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'recur_frequency_unit' => array( + ], + 'recur_frequency_unit' => [ 'name' => 'recur_frequency_unit', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Recurring Frequency') , - 'description' => 'Supported recurring frequency units.', + 'title' => ts('Recurring Frequency'), + 'description' => ts('Supported recurring frequency units.'), 'maxlength' => 128, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.recur_frequency_unit', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_recur_interval' => array( + 'serialize' => self::SERIALIZE_SEPARATOR_TRIMMED, + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'recur_frequency_units', + 'keyColumn' => 'name', + 'optionEditPath' => 'civicrm/admin/options/recur_frequency_units', + ], + ], + 'is_recur_interval' => [ 'name' => 'is_recur_interval', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Support Recurring Intervals') , - 'description' => 'if true - supports recurring intervals', + 'title' => ts('Support Recurring Intervals'), + 'description' => ts('if true - supports recurring intervals'), + 'where' => 'civicrm_contribution_page.is_recur_interval', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_recur_installments' => array( + ], + 'is_recur_installments' => [ 'name' => 'is_recur_installments', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Recurring Installments?') , - 'description' => 'if true - asks user for recurring installments', + 'title' => ts('Recurring Installments?'), + 'description' => ts('if true - asks user for recurring installments'), + 'where' => 'civicrm_contribution_page.is_recur_installments', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'adjust_recur_start_date' => array( + ], + 'adjust_recur_start_date' => [ 'name' => 'adjust_recur_start_date', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Adjust Recurring Start Date') , - 'description' => 'if true - user is able to adjust payment start date', + 'title' => ts('Adjust Recurring Start Date'), + 'description' => ts('if true - user is able to adjust payment start date'), + 'where' => 'civicrm_contribution_page.adjust_recur_start_date', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_pay_later' => array( + ], + 'is_pay_later' => [ 'name' => 'is_pay_later', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Pay Later') , - 'description' => 'if true - allows the user to send payment directly to the org later', + 'title' => ts('Pay Later'), + 'description' => ts('if true - allows the user to send payment directly to the org later'), + 'where' => 'civicrm_contribution_page.is_pay_later', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'pay_later_text' => array( + ], + 'pay_later_text' => [ 'name' => 'pay_later_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Pay Later Text') , - 'description' => 'The text displayed to the user in the main form', + 'title' => ts('Pay Later Text'), + 'description' => ts('The text displayed to the user in the main form'), + 'where' => 'civicrm_contribution_page.pay_later_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'pay_later_receipt' => array( + ], + 'pay_later_receipt' => [ 'name' => 'pay_later_receipt', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Pay Later Receipt') , - 'description' => 'The receipt sent to the user instead of the normal receipt text', + 'title' => ts('Pay Later Receipt'), + 'description' => ts('The receipt sent to the user instead of the normal receipt text'), + 'where' => 'civicrm_contribution_page.pay_later_receipt', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'is_partial_payment' => array( + ], + 'is_partial_payment' => [ 'name' => 'is_partial_payment', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Allow Partial Payment') , - 'description' => 'is partial payment enabled for this online contribution page', + 'title' => ts('Allow Partial Payment'), + 'description' => ts('is partial payment enabled for this online contribution page'), + 'where' => 'civicrm_contribution_page.is_partial_payment', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'initial_amount_label' => array( + ], + 'initial_amount_label' => [ 'name' => 'initial_amount_label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Initial Amount Label') , - 'description' => 'Initial amount label for partial payment', + 'title' => ts('Initial Amount Label'), + 'description' => ts('Initial amount label for partial payment'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.initial_amount_label', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'initial_amount_help_text' => array( + ], + 'initial_amount_help_text' => [ 'name' => 'initial_amount_help_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Initial Amount Help Text') , - 'description' => 'Initial amount help text for partial payment', + 'title' => ts('Initial Amount Help Text'), + 'description' => ts('Initial amount help text for partial payment'), + 'where' => 'civicrm_contribution_page.initial_amount_help_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'min_initial_amount' => array( + ], + 'min_initial_amount' => [ 'name' => 'min_initial_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Min Initial Amount') , - 'description' => 'Minimum initial amount for partial payment', - 'precision' => array( + 'title' => ts('Min Initial Amount'), + 'description' => ts('Minimum initial amount for partial payment'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_contribution_page.min_initial_amount', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_allow_other_amount' => array( + ], + 'is_allow_other_amount' => [ 'name' => 'is_allow_other_amount', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Allow Other Amounts') , - 'description' => 'if true, page will include an input text field where user can enter their own amount', + 'title' => ts('Allow Other Amounts'), + 'description' => ts('if true, page will include an input text field where user can enter their own amount'), + 'where' => 'civicrm_contribution_page.is_allow_other_amount', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'default_amount_id' => array( + ], + 'default_amount_id' => [ 'name' => 'default_amount_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Default Amount') , - 'description' => 'FK to civicrm_option_value.', + 'title' => ts('Default Amount'), + 'description' => ts('FK to civicrm_option_value.'), + 'where' => 'civicrm_contribution_page.default_amount_id', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'min_amount' => array( + ], + 'min_amount' => [ 'name' => 'min_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Minimum Amount') , - 'description' => 'if other amounts allowed, user can configure minimum allowed.', - 'precision' => array( + 'title' => ts('Minimum Amount'), + 'description' => ts('if other amounts allowed, user can configure minimum allowed.'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_contribution_page.min_amount', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'max_amount' => array( + ], + 'max_amount' => [ 'name' => 'max_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Maximum Amount') , - 'description' => 'if other amounts allowed, user can configure maximum allowed.', - 'precision' => array( + 'title' => ts('Maximum Amount'), + 'description' => ts('if other amounts allowed, user can configure maximum allowed.'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_contribution_page.max_amount', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'goal_amount' => array( + ], + 'goal_amount' => [ 'name' => 'goal_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Goal Amount') , - 'description' => 'The target goal for this page, allows people to build a goal meter', - 'precision' => array( + 'title' => ts('Goal Amount'), + 'description' => ts('The target goal for this page, allows people to build a goal meter'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_contribution_page.goal_amount', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'thankyou_title' => array( + ], + 'thankyou_title' => [ 'name' => 'thankyou_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Thank-you Title') , - 'description' => 'Title for Thank-you page (header title tag, and display at the top of the page).', + 'title' => ts('Thank-you Title'), + 'description' => ts('Title for Thank-you page (header title tag, and display at the top of the page).'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.thankyou_title', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'thankyou_text' => array( + ], + 'thankyou_text' => [ 'name' => 'thankyou_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Thank-you Text') , - 'description' => 'text and html allowed. displayed above result on success page', + 'title' => ts('Thank-you Text'), + 'description' => ts('text and html allowed. displayed above result on success page'), 'rows' => 8, 'cols' => 60, + 'where' => 'civicrm_contribution_page.thankyou_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'RichTextEditor', - ) , - ) , - 'thankyou_footer' => array( + ], + ], + 'thankyou_footer' => [ 'name' => 'thankyou_footer', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Thank-you Footer') , - 'description' => 'Text and html allowed. displayed at the bottom of the success page. Common usage is to include link(s) to other pages such as tell-a-friend, etc.', + 'title' => ts('Thank-you Footer'), + 'description' => ts('Text and html allowed. displayed at the bottom of the success page. Common usage is to include link(s) to other pages such as tell-a-friend, etc.'), 'rows' => 8, 'cols' => 60, + 'where' => 'civicrm_contribution_page.thankyou_footer', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'RichTextEditor', - ) , - ) , - 'is_email_receipt' => array( + ], + ], + 'is_email_receipt' => [ 'name' => 'is_email_receipt', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Send email Receipt') , - 'description' => 'if true, receipt is automatically emailed to contact on success', + 'title' => ts('Send email Receipt'), + 'description' => ts('if true, receipt is automatically emailed to contact on success'), + 'where' => 'civicrm_contribution_page.is_email_receipt', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'receipt_from_name' => array( + ], + 'receipt_from_name' => [ 'name' => 'receipt_from_name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Receipt From') , - 'description' => 'FROM email name used for receipts generated by contributions to this contribution page.', + 'title' => ts('Receipt From'), + 'description' => ts('FROM email name used for receipts generated by contributions to this contribution page.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.receipt_from_name', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - ) , - 'receipt_from_email' => array( + ], + 'receipt_from_email' => [ 'name' => 'receipt_from_email', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Receipt From email') , - 'description' => 'FROM email address used for receipts generated by contributions to this contribution page.', + 'title' => ts('Receipt From email'), + 'description' => ts('FROM email address used for receipts generated by contributions to this contribution page.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.receipt_from_email', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'cc_receipt' => array( + ], + 'cc_receipt' => [ 'name' => 'cc_receipt', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Receipt cc') , - 'description' => 'comma-separated list of email addresses to cc each time a receipt is sent', + 'title' => ts('Receipt cc'), + 'description' => ts('comma-separated list of email addresses to cc each time a receipt is sent'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.cc_receipt', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'bcc_receipt' => array( + ], + 'bcc_receipt' => [ 'name' => 'bcc_receipt', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Receipt bcc') , - 'description' => 'comma-separated list of email addresses to bcc each time a receipt is sent', + 'title' => ts('Receipt bcc'), + 'description' => ts('comma-separated list of email addresses to bcc each time a receipt is sent'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.bcc_receipt', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'receipt_text' => array( + ], + 'receipt_text' => [ 'name' => 'receipt_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Receipt Text') , - 'description' => 'text to include above standard receipt info on receipt email. emails are text-only, so do not allow html for now', + 'title' => ts('Receipt Text'), + 'description' => ts('text to include above standard receipt info on receipt email. emails are text-only, so do not allow html for now'), 'rows' => 6, 'cols' => 50, + 'where' => 'civicrm_contribution_page.receipt_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'TextArea', - ) , - ) , - 'is_active' => array( + ], + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Page Active?') , - 'description' => 'Is this property active?', + 'title' => ts('Is Page Active?'), + 'description' => ts('Is this property active?'), + 'where' => 'civicrm_contribution_page.is_active', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'footer_text' => array( + ], + 'footer_text' => [ 'name' => 'footer_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Footer Text') , - 'description' => 'Text and html allowed. Displayed at the bottom of the first page of the contribution wizard.', + 'title' => ts('Footer Text'), + 'description' => ts('Text and html allowed. Displayed at the bottom of the first page of the contribution wizard.'), 'rows' => 6, 'cols' => 50, + 'where' => 'civicrm_contribution_page.footer_text', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 1, - 'html' => array( + 'html' => [ 'type' => 'RichTextEditor', - ) , - ) , - 'amount_block_is_active' => array( + ], + ], + 'amount_block_is_active' => [ 'name' => 'amount_block_is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Amount Block Active?') , - 'description' => 'Is this property active?', + 'title' => ts('Is Amount Block Active?'), + 'description' => ts('Is this property active?'), + 'where' => 'civicrm_contribution_page.amount_block_is_active', 'default' => '1', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'start_date' => array( + ], + 'start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Contribution Page Start Date') , - 'description' => 'Date and time that this page starts.', + 'title' => ts('Contribution Page Start Date'), + 'description' => ts('Date and time that this page starts.'), + 'where' => 'civicrm_contribution_page.start_date', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'end_date' => array( + ], + 'end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Contribution Page End Date') , - 'description' => 'Date and time that this page ends. May be NULL if no defined end date/time', + 'title' => ts('Contribution Page End Date'), + 'description' => ts('Date and time that this page ends. May be NULL if no defined end date/time'), + 'where' => 'civicrm_contribution_page.end_date', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'created_id' => array( + ], + 'created_id' => [ 'name' => 'created_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Page Created By') , - 'description' => 'FK to civicrm_contact, who created this contribution page', + 'title' => ts('Contribution Page Created By'), + 'description' => ts('FK to civicrm_contact, who created this contribution page'), + 'where' => 'civicrm_contribution_page.created_id', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'created_date' => array( + ], + 'created_date' => [ 'name' => 'created_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Contribution Page Created Date') , - 'description' => 'Date and time that contribution page was created.', + 'title' => ts('Contribution Page Created Date'), + 'description' => ts('Date and time that contribution page was created.'), + 'where' => 'civicrm_contribution_page.created_date', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'currency' => array( + ], + 'currency' => [ 'name' => 'currency', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Contribution Page Currency') , - 'description' => '3 character string, value from config setting or input via user.', + 'title' => ts('Contribution Page Currency'), + 'description' => ts('3 character string, value from config setting or input via user.'), 'maxlength' => 3, 'size' => CRM_Utils_Type::FOUR, + 'where' => 'civicrm_contribution_page.currency', + 'headerPattern' => '/cur(rency)?/i', + 'dataPattern' => '/^[A-Z]{3}$/i', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_currency', 'keyColumn' => 'name', 'labelColumn' => 'full_name', 'nameColumn' => 'name', - ) - ) , - 'campaign_id' => array( + 'abbrColumn' => 'symbol', + ], + ], + 'campaign_id' => [ 'name' => 'campaign_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Page Campaign ID') , - 'description' => 'The campaign for which we are collecting contributions with this page.', + 'title' => ts('Contribution Page Campaign ID'), + 'description' => ts('The campaign for which we are collecting contributions with this page.'), + 'where' => 'civicrm_contribution_page.campaign_id', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, 'FKClassName' => 'CRM_Campaign_DAO_Campaign', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_campaign', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'is_share' => array( + ], + ], + 'is_share' => [ 'name' => 'is_share', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Contribution Page Shared?') , - 'description' => 'Can people share the contribution page through social media?', + 'title' => ts('Is Contribution Page Shared?'), + 'description' => ts('Can people share the contribution page through social media?'), + 'where' => 'civicrm_contribution_page.is_share', 'default' => '1', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - 'is_billing_required' => array( + ], + 'is_billing_required' => [ 'name' => 'is_billing_required', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is billing block required') , - 'description' => 'if true - billing block is required for online contribution page', + 'title' => ts('Is billing block required'), + 'description' => ts('if true - billing block is required for online contribution page'), + 'where' => 'civicrm_contribution_page.is_billing_required', + 'default' => '0', 'table_name' => 'civicrm_contribution_page', 'entity' => 'ContributionPage', 'bao' => 'CRM_Contribute_BAO_ContributionPage', 'localizable' => 0, - ) , - ); + ], + 'contribution_page_frontend_title' => [ + 'name' => 'frontend_title', + 'type' => CRM_Utils_Type::T_STRING, + 'title' => ts('Public Title'), + 'description' => ts('Contribution Page Public title'), + 'maxlength' => 255, + 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_page.frontend_title', + 'default' => 'NULL', + 'table_name' => 'civicrm_contribution_page', + 'entity' => 'ContributionPage', + 'bao' => 'CRM_Contribute_BAO_ContributionPage', + 'localizable' => 1, + 'html' => [ + 'type' => 'Text', + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -941,10 +1061,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_page', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_page', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -952,15 +1073,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_page', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_page', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/ContributionProduct.php b/CRM/Contribute/DAO/ContributionProduct.php index 9061090543f5..5d21e66a1b3b 100644 --- a/CRM/Contribute/DAO/ContributionProduct.php +++ b/CRM/Contribute/DAO/ContributionProduct.php @@ -1,301 +1,289 @@ __table = 'civicrm_contribution_product'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contribution_id', 'civicrm_contribution', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_id', 'civicrm_contribution', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Product ID') , - 'required' => true, + 'title' => ts('Contribution Product ID'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_product.id', 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'product_id' => array( + ], + 'product_id' => [ 'name' => 'product_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Product ID') , - 'required' => true, + 'title' => ts('Product ID'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_product.product_id', 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'contribution_id' => array( + ], + 'contribution_id' => [ 'name' => 'contribution_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution ID') , - 'required' => true, + 'title' => ts('Contribution ID'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_product.contribution_id', 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_Contribution', - ) , - 'product_option' => array( + ], + 'product_option' => [ 'name' => 'product_option', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Product Option') , - 'description' => 'Option value selected if applicable - e.g. color, size etc.', + 'title' => ts('Product Option'), + 'description' => ts('Option value selected if applicable - e.g. color, size etc.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'export' => true, 'where' => 'civicrm_contribution_product.product_option', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'quantity' => array( + ], + 'quantity' => [ 'name' => 'quantity', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Quantity') , - 'export' => true, + 'title' => ts('Quantity'), 'where' => 'civicrm_contribution_product.quantity', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'fulfilled_date' => array( + ], + 'fulfilled_date' => [ 'name' => 'fulfilled_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Fulfilled Date') , - 'description' => 'Optional. Can be used to record the date this product was fulfilled or shipped.', - 'export' => true, + 'title' => ts('Fulfilled Date'), + 'description' => ts('Optional. Can be used to record the date this product was fulfilled or shipped.'), 'where' => 'civicrm_contribution_product.fulfilled_date', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'contribution_start_date' => array( + 'html' => [ + 'type' => 'Select Date', + 'formatType' => 'activityDate', + ], + ], + 'contribution_start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('Start date for premium') , - 'description' => 'Actual start date for a time-delimited premium (subscription, service or membership)', - 'export' => true, + 'title' => ts('Start date for premium'), + 'description' => ts('Actual start date for a time-delimited premium (subscription, service or membership)'), 'where' => 'civicrm_contribution_product.start_date', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'contribution_end_date' => array( + ], + 'contribution_end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE, - 'title' => ts('End date for premium') , - 'description' => 'Actual end date for a time-delimited premium (subscription, service or membership)', - 'export' => true, + 'title' => ts('End date for premium'), + 'description' => ts('Actual end date for a time-delimited premium (subscription, service or membership)'), 'where' => 'civicrm_contribution_product.end_date', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'comment' => array( + ], + 'comment' => [ 'name' => 'comment', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Premium comment') , + 'title' => ts('Premium comment'), + 'where' => 'civicrm_contribution_product.comment', 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, - ) , - 'financial_type_id' => array( + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'FK to Financial Type(for membership price sets only).', + 'title' => ts('Financial Type'), + 'description' => ts('FK to Financial Type(for membership price sets only).'), + 'where' => 'civicrm_contribution_product.financial_type_id', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_product', 'entity' => 'ContributionProduct', 'bao' => 'CRM_Contribute_DAO_ContributionProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -303,10 +291,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_product', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_product', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -314,15 +303,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_product', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_product', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/ContributionRecur.php b/CRM/Contribute/DAO/ContributionRecur.php index 5bdae065b527..a560211bdb59 100644 --- a/CRM/Contribute/DAO/ContributionRecur.php +++ b/CRM/Contribute/DAO/ContributionRecur.php @@ -1,699 +1,794 @@ __table = 'civicrm_contribution_recur'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'payment_token_id', 'civicrm_payment_token', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'payment_processor_id', 'civicrm_payment_processor', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'campaign_id', 'civicrm_campaign', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'payment_token_id', 'civicrm_payment_token', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'payment_processor_id', 'civicrm_payment_processor', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'campaign_id', 'civicrm_campaign', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'contribution_recur_id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Recurring Contribution ID') , - 'description' => 'Contribution Recur ID', - 'required' => true, + 'title' => ts('Recurring Contribution ID'), + 'description' => ts('Contribution Recur ID'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - ) , - 'contact_id' => array( + ], + 'contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'Foreign key to civicrm_contact.id .', - 'required' => true, + 'title' => ts('Contact'), + 'description' => ts('Foreign key to civicrm_contact.id.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.contact_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'amount' => array( + 'html' => [ + 'type' => 'EntityRef', + ], + ], + 'amount' => [ 'name' => 'amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Amount') , - 'description' => 'Amount to be contributed or charged each recurrence.', - 'required' => true, - 'precision' => array( + 'title' => ts('Amount'), + 'description' => ts('Amount to be contributed or charged each recurrence.'), + 'required' => TRUE, + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_contribution_recur.amount', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'currency' => array( + ], + ], + 'currency' => [ 'name' => 'currency', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Currency') , - 'description' => '3 character string, value from config setting or input via user.', + 'title' => ts('Currency'), + 'description' => ts('3 character string, value from config setting or input via user.'), 'maxlength' => 3, 'size' => CRM_Utils_Type::FOUR, + 'where' => 'civicrm_contribution_recur.currency', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_currency', 'keyColumn' => 'name', 'labelColumn' => 'full_name', 'nameColumn' => 'name', - ) - ) , - 'frequency_unit' => array( + 'abbrColumn' => 'symbol', + ], + ], + 'frequency_unit' => [ 'name' => 'frequency_unit', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Frequency Unit') , - 'description' => 'Time units for recurrence of payment.', + 'title' => ts('Frequency Unit'), + 'description' => ts('Time units for recurrence of payment.'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_contribution_recur.frequency_unit', 'default' => 'month', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'recur_frequency_units', 'keyColumn' => 'name', 'optionEditPath' => 'civicrm/admin/options/recur_frequency_units', - ) - ) , - 'frequency_interval' => array( + ], + ], + 'frequency_interval' => [ 'name' => 'frequency_interval', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Interval (number of units)') , - 'description' => 'Number of time units for recurrence of payment.', - 'required' => true, + 'title' => ts('Interval (number of units)'), + 'description' => ts('Number of time units for recurrence of payment.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.frequency_interval', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'installments' => array( + ], + ], + 'installments' => [ 'name' => 'installments', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Number of Installments') , - 'description' => 'Total number of payments to be made. Set this to 0 if this is an open-ended commitment i.e. no set end date.', + 'title' => ts('Number of Installments'), + 'description' => ts('Total number of payments to be made. Set this to 0 if this is an open-ended commitment i.e. no set end date.'), + 'where' => 'civicrm_contribution_recur.installments', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'start_date' => array( + ], + ], + 'contribution_recur_start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Recurring Contribution Started Date') , - 'description' => 'The date the first scheduled recurring contribution occurs.', - 'required' => true, + 'title' => ts('Start Date'), + 'description' => ts('The date the first scheduled recurring contribution occurs.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.start_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Recurring Contribution Start Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'create_date' => array( + 'formatType' => 'activityDateTime', + ], + ], + 'contribution_recur_create_date' => [ 'name' => 'create_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Recurring Contribution Created Date') , - 'description' => 'When this recurring contribution record was created.', - 'required' => true, + 'title' => ts('Created Date'), + 'description' => ts('When this recurring contribution record was created.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.create_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Recurring Contribution Create Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'modified_date' => array( + 'formatType' => 'activityDateTime', + ], + ], + 'contribution_recur_modified_date' => [ 'name' => 'modified_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Recurring Contribution Modified Date') , - 'description' => 'Last updated date for this record. mostly the last time a payment was received', + 'title' => ts('Modified Date'), + 'description' => ts('Last updated date for this record. mostly the last time a payment was received'), + 'where' => 'civicrm_contribution_recur.modified_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Recurring Contribution Modified Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'cancel_date' => array( + 'formatType' => 'activityDateTime', + ], + ], + 'contribution_recur_cancel_date' => [ 'name' => 'cancel_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Recurring Contribution Cancel Date') , - 'description' => 'Date this recurring contribution was cancelled by contributor- if we can get access to it', + 'title' => ts('Cancel Date'), + 'description' => ts('Date this recurring contribution was cancelled by contributor- if we can get access to it'), + 'where' => 'civicrm_contribution_recur.cancel_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Recurring Contribution Cancel Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'end_date' => array( + 'formatType' => 'activityDate', + ], + ], + 'contribution_recur_cancel_reason' => [ + 'name' => 'cancel_reason', + 'type' => CRM_Utils_Type::T_TEXT, + 'title' => ts('Cancellation Reason'), + 'description' => ts('Free text field for a reason for cancelling'), + 'where' => 'civicrm_contribution_recur.cancel_reason', + 'table_name' => 'civicrm_contribution_recur', + 'entity' => 'ContributionRecur', + 'bao' => 'CRM_Contribute_BAO_ContributionRecur', + 'localizable' => 0, + 'unique_title' => ts('Recurring Contribution Cancel Reason'), + 'html' => [ + 'type' => 'Text', + ], + ], + 'contribution_recur_end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Recurring Contribution End Date') , - 'description' => 'Date this recurring contribution finished successfully', + 'title' => ts('Recurring Contribution End Date'), + 'description' => ts('Date this recurring contribution finished successfully'), + 'where' => 'civicrm_contribution_recur.end_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Recurring Contribution End Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'processor_id' => array( + 'formatType' => 'activityDate', + ], + ], + 'contribution_recur_processor_id' => [ 'name' => 'processor_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Processor ID') , - 'description' => 'Possibly needed to store a unique identifier for this recurring payment order - if this is available from the processor??', + 'title' => ts('Processor ID'), + 'description' => ts('Possibly needed to store a unique identifier for this recurring payment order - if this is available from the processor??'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_recur.processor_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - ) , - 'payment_token_id' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'payment_token_id' => [ 'name' => 'payment_token_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Payment Token ID') , - 'description' => 'Optionally used to store a link to a payment token used for this recurring contribution.', + 'title' => ts('Payment Token ID'), + 'description' => ts('Optionally used to store a link to a payment token used for this recurring contribution.'), + 'where' => 'civicrm_contribution_recur.payment_token_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_PaymentToken', - ) , - 'trxn_id' => array( + ], + 'contribution_recur_trxn_id' => [ 'name' => 'trxn_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Transaction ID') , - 'description' => 'unique transaction id. may be processor id, bank id + trans id, or account number + check number... depending on payment_method', + 'title' => ts('Transaction ID'), + 'description' => ts('unique transaction id. may be processor id, bank id + trans id, or account number + check number... depending on payment_method'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_recur.trxn_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - ) , - 'invoice_id' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'invoice_id' => [ 'name' => 'invoice_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Invoice ID') , - 'description' => 'unique invoice id, system generated or passed in', + 'title' => ts('Invoice ID'), + 'description' => ts('unique invoice id, system generated or passed in'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_recur.invoice_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - ) , - 'contribution_status_id' => array( + 'html' => [ + 'type' => 'Text', + ], + ], + 'contribution_recur_contribution_status_id' => [ 'name' => 'contribution_status_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Recurring Contribution Status') , - 'import' => true, + 'title' => ts('Status'), + 'import' => TRUE, 'where' => 'civicrm_contribution_recur.contribution_status_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'pseudoconstant' => array( - 'optionGroupName' => 'contribution_status', - 'optionEditPath' => 'civicrm/admin/options/contribution_status', - ) - ) , - 'is_test' => array( + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'optionGroupName' => 'contribution_recur_status', + 'optionEditPath' => 'civicrm/admin/options/contribution_recur_status', + ], + ], + 'is_test' => [ 'name' => 'is_test', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Test') , - 'import' => true, + 'title' => ts('Test'), + 'import' => TRUE, 'where' => 'civicrm_contribution_recur.is_test', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'default' => '0', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'cycle_day' => array( + ], + ], + 'cycle_day' => [ 'name' => 'cycle_day', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Number of Cycle Day') , - 'description' => 'Day in the period when the payment should be charged e.g. 1st of month, 15th etc.', - 'required' => true, + 'title' => ts('Cycle Day'), + 'description' => ts('Day in the period when the payment should be charged e.g. 1st of month, 15th etc.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.cycle_day', 'default' => '1', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'next_sched_contribution_date' => array( + ], + ], + 'contribution_recur_next_sched_contribution_date' => [ 'name' => 'next_sched_contribution_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Next Scheduled Contribution Date') , - 'description' => 'Next scheduled date', + 'title' => ts('Next Scheduled Contribution Date'), + 'description' => ts('Next scheduled date'), + 'where' => 'civicrm_contribution_recur.next_sched_contribution_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Next Scheduled Recurring Contribution'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'failure_count' => array( + 'formatType' => 'activityDate', + ], + ], + 'failure_count' => [ 'name' => 'failure_count', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Number of Failures') , - 'description' => 'Number of failed charge attempts since last success. Business rule could be set to deactivate on more than x failures.', + 'title' => ts('Number of Failures'), + 'description' => ts('Number of failed charge attempts since last success. Business rule could be set to deactivate on more than x failures.'), + 'where' => 'civicrm_contribution_recur.failure_count', + 'default' => '0', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Text', - ) , - ) , - 'failure_retry_date' => array( + ], + ], + 'contribution_recur_failure_retry_date' => [ 'name' => 'failure_retry_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => ts('Retry Failed Attempt Date') , - 'description' => 'Date to retry failed attempt', + 'title' => ts('Retry Failed Attempt Date'), + 'description' => ts('Date to retry failed attempt'), + 'where' => 'civicrm_contribution_recur.failure_retry_date', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'unique_title' => ts('Failed Recurring Contribution Retry Date'), + 'html' => [ 'type' => 'Select Date', - ) , - ) , - 'auto_renew' => array( + 'formatType' => 'activityDate', + ], + ], + 'auto_renew' => [ 'name' => 'auto_renew', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Auto Renew') , - 'description' => 'Some systems allow contributor to set a number of installments - but then auto-renew the subscription or commitment if they do not cancel.', - 'required' => true, + 'title' => ts('Auto Renew'), + 'description' => ts('Some systems allow contributor to set a number of installments - but then auto-renew the subscription or commitment if they do not cancel.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_recur.auto_renew', + 'default' => '0', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - 'payment_processor_id' => array( + ], + ], + 'contribution_recur_payment_processor_id' => [ 'name' => 'payment_processor_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Payment Processor') , - 'description' => 'Foreign key to civicrm_payment_processor.id', + 'title' => ts('Payment Processor'), + 'description' => ts('Foreign key to civicrm_payment_processor.id'), + 'where' => 'civicrm_contribution_recur.payment_processor_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_PaymentProcessor', - ) , - 'financial_type_id' => array( + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ + 'table' => 'civicrm_payment_processor', + 'keyColumn' => 'id', + 'labelColumn' => 'name', + ], + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'FK to Financial Type', - 'export' => false, + 'title' => ts('Financial Type'), + 'description' => ts('FK to Financial Type'), 'where' => 'civicrm_contribution_recur.financial_type_id', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => FALSE, 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'pseudoconstant' => array( + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - 'payment_instrument_id' => array( + ], + ], + 'payment_instrument_id' => [ 'name' => 'payment_instrument_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Payment Method') , - 'description' => 'FK to Payment Instrument', + 'title' => ts('Payment Method'), + 'description' => ts('FK to Payment Instrument'), + 'where' => 'civicrm_contribution_recur.payment_instrument_id', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'optionGroupName' => 'payment_instrument', 'optionEditPath' => 'civicrm/admin/options/payment_instrument', - ) - ) , - 'contribution_campaign_id' => array( + ], + ], + 'contribution_campaign_id' => [ 'name' => 'campaign_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Campaign') , - 'description' => 'The campaign for which this contribution has been triggered.', - 'import' => true, + 'title' => ts('Campaign'), + 'description' => ts('The campaign for which this contribution has been triggered.'), + 'import' => TRUE, 'where' => 'civicrm_contribution_recur.campaign_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, 'FKClassName' => 'CRM_Campaign_DAO_Campaign', - 'pseudoconstant' => array( + 'html' => [ + 'type' => 'Select', + ], + 'pseudoconstant' => [ 'table' => 'civicrm_campaign', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'is_email_receipt' => array( + ], + ], + 'is_email_receipt' => [ 'name' => 'is_email_receipt', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Send email Receipt?') , - 'description' => 'if true, receipt is automatically emailed to contact on each successful payment', + 'title' => ts('Send email Receipt?'), + 'description' => ts('if true, receipt is automatically emailed to contact on each successful payment'), + 'where' => 'civicrm_contribution_recur.is_email_receipt', 'default' => '1', 'table_name' => 'civicrm_contribution_recur', 'entity' => 'ContributionRecur', 'bao' => 'CRM_Contribute_BAO_ContributionRecur', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'CheckBox', - ) , - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -701,10 +796,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_recur', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_recur', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -712,50 +808,56 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_recur', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_recur', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'UI_contrib_trxn_id' => array( + $indices = [ + 'UI_contrib_trxn_id' => [ 'name' => 'UI_contrib_trxn_id', - 'field' => array( + 'field' => [ 0 => 'trxn_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contribution_recur::1::trxn_id', - ) , - 'UI_contrib_invoice_id' => array( + ], + 'UI_contrib_invoice_id' => [ 'name' => 'UI_contrib_invoice_id', - 'field' => array( + 'field' => [ 0 => 'invoice_id', - ) , - 'localizable' => false, - 'unique' => true, + ], + 'localizable' => FALSE, + 'unique' => TRUE, 'sig' => 'civicrm_contribution_recur::1::invoice_id', - ) , - 'index_contribution_status' => array( + ], + 'index_contribution_status' => [ 'name' => 'index_contribution_status', - 'field' => array( + 'field' => [ 0 => 'contribution_status_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution_recur::0::contribution_status_id', - ) , - 'UI_contribution_recur_payment_instrument_id' => array( + ], + 'UI_contribution_recur_payment_instrument_id' => [ 'name' => 'UI_contribution_recur_payment_instrument_id', - 'field' => array( + 'field' => [ 0 => 'payment_instrument_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution_recur::0::payment_instrument_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/ContributionSoft.php b/CRM/Contribute/DAO/ContributionSoft.php index 2d8f1f692bad..651db6a88387 100644 --- a/CRM/Contribute/DAO/ContributionSoft.php +++ b/CRM/Contribute/DAO/ContributionSoft.php @@ -1,328 +1,329 @@ __table = 'civicrm_contribution_soft'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contribution_id', 'civicrm_contribution', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contact_id', 'civicrm_contact', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'pcp_id', 'civicrm_pcp', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_id', 'civicrm_contribution', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'pcp_id', 'civicrm_pcp', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'contribution_soft_id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'contribution_soft_id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Soft Contribution ID') , - 'description' => 'Soft Contribution ID', - 'required' => true, - 'import' => true, + 'title' => ts('Soft Contribution ID'), + 'description' => ts('Soft Contribution ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contribution_soft.id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - ) , - 'contribution_id' => array( + ], + 'contribution_id' => [ 'name' => 'contribution_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Soft Contribution - Contribution') , - 'description' => 'FK to contribution table.', - 'required' => true, + 'title' => ts('Soft Contribution - Contribution'), + 'description' => ts('FK to contribution table.'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_soft.contribution_id', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_Contribution', - ) , - 'contribution_soft_contact_id' => array( + ], + 'contribution_soft_contact_id' => [ 'name' => 'contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contact ID') , - 'description' => 'FK to Contact ID', - 'required' => true, - 'import' => true, + 'title' => ts('Contact ID'), + 'description' => ts('FK to Contact ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_contribution_soft.contact_id', 'headerPattern' => '/contact(.?id)?/i', 'dataPattern' => '/^\d+$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'amount' => array( + ], + 'amount' => [ 'name' => 'amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Soft Contribution Amount') , - 'description' => 'Amount of this soft contribution.', - 'required' => true, - 'precision' => array( + 'title' => ts('Soft Contribution Amount'), + 'description' => ts('Amount of this soft contribution.'), + 'required' => TRUE, + 'precision' => [ 20, - 2 - ) , - 'import' => true, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_contribution_soft.amount', 'headerPattern' => '/total(.?am(ou)?nt)?/i', 'dataPattern' => '/^\d+(\.\d{2})?$/', - 'export' => true, + 'export' => TRUE, 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - ) , - 'currency' => array( + ], + 'currency' => [ 'name' => 'currency', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Soft Contribution Currency') , - 'description' => '3 character string, value from config setting or input via user.', + 'title' => ts('Soft Contribution Currency'), + 'description' => ts('3 character string, value from config setting or input via user.'), 'maxlength' => 3, 'size' => CRM_Utils_Type::FOUR, + 'where' => 'civicrm_contribution_soft.currency', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_currency', 'keyColumn' => 'name', 'labelColumn' => 'full_name', 'nameColumn' => 'name', - ) - ) , - 'pcp_id' => array( + 'abbrColumn' => 'symbol', + ], + ], + 'pcp_id' => [ 'name' => 'pcp_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Soft Contribution PCP') , - 'description' => 'FK to civicrm_pcp.id', + 'title' => ts('Soft Contribution PCP'), + 'description' => ts('FK to civicrm_pcp.id'), + 'where' => 'civicrm_contribution_soft.pcp_id', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, 'FKClassName' => 'CRM_PCP_DAO_PCP', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_pcp', 'keyColumn' => 'id', 'labelColumn' => 'title', - ) - ) , - 'pcp_display_in_roll' => array( + ], + ], + 'pcp_display_in_roll' => [ 'name' => 'pcp_display_in_roll', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Soft Contribution Display on PCP') , + 'title' => ts('Soft Contribution Display on PCP'), + 'where' => 'civicrm_contribution_soft.pcp_display_in_roll', + 'default' => '0', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - ) , - 'pcp_roll_nickname' => array( + ], + 'pcp_roll_nickname' => [ 'name' => 'pcp_roll_nickname', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Soft Contribution PCP Nickname') , + 'title' => ts('Soft Contribution PCP Nickname'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_soft.pcp_roll_nickname', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - ) , - 'pcp_personal_note' => array( + ], + 'pcp_personal_note' => [ 'name' => 'pcp_personal_note', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Soft Contribution PCP Note') , + 'title' => ts('Soft Contribution PCP Note'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_soft.pcp_personal_note', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - ) , - 'soft_credit_type_id' => array( + 'html' => [ + 'type' => 'TextArea', + ], + ], + 'soft_credit_type_id' => [ 'name' => 'soft_credit_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Soft Credit Type') , - 'description' => 'Soft Credit Type ID.Implicit FK to civicrm_option_value where option_group = soft_credit_type.', + 'title' => ts('Soft Credit Type'), + 'description' => ts('Soft Credit Type ID.Implicit FK to civicrm_option_value where option_group = soft_credit_type.'), + 'where' => 'civicrm_contribution_soft.soft_credit_type_id', 'default' => 'NULL', 'table_name' => 'civicrm_contribution_soft', 'entity' => 'ContributionSoft', 'bao' => 'CRM_Contribute_BAO_ContributionSoft', 'localizable' => 0, - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'optionGroupName' => 'soft_credit_type', 'optionEditPath' => 'civicrm/admin/options/soft_credit_type', - ) - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -330,10 +331,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_soft', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_soft', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -341,24 +343,30 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_soft', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_soft', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array( - 'index_id' => array( + $indices = [ + 'index_id' => [ 'name' => 'index_id', - 'field' => array( + 'field' => [ 0 => 'pcp_id', - ) , - 'localizable' => false, + ], + 'localizable' => FALSE, 'sig' => 'civicrm_contribution_soft::0::pcp_id', - ) , - ); + ], + ]; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/Premium.php b/CRM/Contribute/DAO/Premium.php index 8de2ba9601db..2e6975402672 100644 --- a/CRM/Contribute/DAO/Premium.php +++ b/CRM/Contribute/DAO/Premium.php @@ -1,303 +1,307 @@ at top of Premiums section of page. Text and HTML allowed. * * @var text */ public $premiums_intro_text; + /** * This email address is included in receipts if it is populated and a premium has been selected. * * @var string */ public $premiums_contact_email; + /** * This phone number is included in receipts if it is populated and a premium has been selected. * * @var string */ public $premiums_contact_phone; + /** * Boolean. Should we automatically display minimum contribution amount text after the premium descriptions. * - * @var boolean + * @var bool */ public $premiums_display_min_contribution; + /** * Label displayed for No Thank-you option in premiums block (e.g. No thank you) * * @var string */ public $premiums_nothankyou_label; + /** - * - * @var int unsigned + * @var int */ public $premiums_nothankyou_position; + /** * Class constructor. */ - function __construct() { + public function __construct() { $this->__table = 'civicrm_premiums'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName() , 'entity_id', NULL, 'id', 'entity_table'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Dynamic(self::getTableName(), 'entity_id', NULL, 'id', 'entity_table'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Premium ID') , - 'required' => true, + 'title' => ts('Premium ID'), + 'required' => TRUE, + 'where' => 'civicrm_premiums.id', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'entity_table' => array( + ], + 'entity_table' => [ 'name' => 'entity_table', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Premium Entity') , - 'description' => 'Joins these premium settings to another object. Always civicrm_contribution_page for now.', - 'required' => true, + 'title' => ts('Premium Entity'), + 'description' => ts('Joins these premium settings to another object. Always civicrm_contribution_page for now.'), + 'required' => TRUE, 'maxlength' => 64, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_premiums.entity_table', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'entity_id' => array( + ], + 'entity_id' => [ 'name' => 'entity_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Premium entity ID') , - 'required' => true, + 'title' => ts('Premium entity ID'), + 'required' => TRUE, + 'where' => 'civicrm_premiums.entity_id', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'premiums_active' => array( + ], + 'premiums_active' => [ 'name' => 'premiums_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Premium Active?') , - 'description' => 'Is the Premiums feature enabled for this page?', - 'required' => true, + 'title' => ts('Is Premium Active?'), + 'description' => ts('Is the Premiums feature enabled for this page?'), + 'required' => TRUE, + 'where' => 'civicrm_premiums.premiums_active', + 'default' => '0', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'premiums_intro_title' => array( + ], + 'premiums_intro_title' => [ 'name' => 'premiums_intro_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Title for Premiums section') , - 'description' => 'Title for Premiums section.', + 'title' => ts('Title for Premiums section'), + 'description' => ts('Title for Premiums section.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_premiums.premiums_intro_title', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 1, - ) , - 'premiums_intro_text' => array( + ], + 'premiums_intro_text' => [ 'name' => 'premiums_intro_text', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Premium Introductory Text') , - 'description' => 'Displayed in
    at top of Premiums section of page. Text and HTML allowed.', + 'title' => ts('Premium Introductory Text'), + 'description' => ts('Displayed in
    at top of Premiums section of page. Text and HTML allowed.'), + 'where' => 'civicrm_premiums.premiums_intro_text', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 1, - ) , - 'premiums_contact_email' => array( + ], + 'premiums_contact_email' => [ 'name' => 'premiums_contact_email', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Premium Contact Email') , - 'description' => 'This email address is included in receipts if it is populated and a premium has been selected.', + 'title' => ts('Premium Contact Email'), + 'description' => ts('This email address is included in receipts if it is populated and a premium has been selected.'), 'maxlength' => 100, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_premiums.premiums_contact_email', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'premiums_contact_phone' => array( + ], + 'premiums_contact_phone' => [ 'name' => 'premiums_contact_phone', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Premiums Contact Phone') , - 'description' => 'This phone number is included in receipts if it is populated and a premium has been selected.', + 'title' => ts('Premiums Contact Phone'), + 'description' => ts('This phone number is included in receipts if it is populated and a premium has been selected.'), 'maxlength' => 50, 'size' => CRM_Utils_Type::BIG, + 'where' => 'civicrm_premiums.premiums_contact_phone', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'premiums_display_min_contribution' => array( + ], + 'premiums_display_min_contribution' => [ 'name' => 'premiums_display_min_contribution', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Display Minimum Contribution?') , - 'description' => 'Boolean. Should we automatically display minimum contribution amount text after the premium descriptions.', - 'required' => true, + 'title' => ts('Display Minimum Contribution?'), + 'description' => ts('Boolean. Should we automatically display minimum contribution amount text after the premium descriptions.'), + 'required' => TRUE, + 'where' => 'civicrm_premiums.premiums_display_min_contribution', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - 'premiums_nothankyou_label' => array( + ], + 'premiums_nothankyou_label' => [ 'name' => 'premiums_nothankyou_label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('No Thank-you Text') , - 'description' => 'Label displayed for No Thank-you option in premiums block (e.g. No thank you)', + 'title' => ts('No Thank-you Text'), + 'description' => ts('Label displayed for No Thank-you option in premiums block (e.g. No thank you)'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_premiums.premiums_nothankyou_label', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 1, - ) , - 'premiums_nothankyou_position' => array( + ], + 'premiums_nothankyou_position' => [ 'name' => 'premiums_nothankyou_position', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('No Thank-you Position') , + 'title' => ts('No Thank-you Position'), + 'where' => 'civicrm_premiums.premiums_nothankyou_position', 'default' => '1', 'table_name' => 'civicrm_premiums', 'entity' => 'Premium', 'bao' => 'CRM_Contribute_BAO_Premium', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -305,10 +309,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'premiums', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'premiums', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -316,15 +321,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'premiums', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'premiums', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/PremiumsProduct.php b/CRM/Contribute/DAO/PremiumsProduct.php index 65e0ca345820..134ac9f52cf3 100644 --- a/CRM/Contribute/DAO/PremiumsProduct.php +++ b/CRM/Contribute/DAO/PremiumsProduct.php @@ -1,210 +1,203 @@ __table = 'civicrm_premiums_product'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'premiums_id', 'civicrm_premiums', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'product_id', 'civicrm_product', 'id'); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'premiums_id', 'civicrm_premiums', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'product_id', 'civicrm_product', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Premium Product ID') , - 'description' => 'Contribution ID', - 'required' => true, + 'title' => ts('Premium Product ID'), + 'description' => ts('Contribution ID'), + 'required' => TRUE, + 'where' => 'civicrm_premiums_product.id', 'table_name' => 'civicrm_premiums_product', 'entity' => 'PremiumsProduct', 'bao' => 'CRM_Contribute_DAO_PremiumsProduct', 'localizable' => 0, - ) , - 'premiums_id' => array( + ], + 'premiums_id' => [ 'name' => 'premiums_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Premium') , - 'description' => 'Foreign key to premiums settings record.', - 'required' => true, + 'title' => ts('Premium'), + 'description' => ts('Foreign key to premiums settings record.'), + 'required' => TRUE, + 'where' => 'civicrm_premiums_product.premiums_id', 'table_name' => 'civicrm_premiums_product', 'entity' => 'PremiumsProduct', 'bao' => 'CRM_Contribute_DAO_PremiumsProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_Premium', - ) , - 'product_id' => array( + ], + 'product_id' => [ 'name' => 'product_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Product') , - 'description' => 'Foreign key to each product object.', - 'required' => true, + 'title' => ts('Product'), + 'description' => ts('Foreign key to each product object.'), + 'required' => TRUE, + 'where' => 'civicrm_premiums_product.product_id', 'table_name' => 'civicrm_premiums_product', 'entity' => 'PremiumsProduct', 'bao' => 'CRM_Contribute_DAO_PremiumsProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_Product', - ) , - 'weight' => array( + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Order') , - 'required' => true, + 'title' => ts('Order'), + 'required' => TRUE, + 'where' => 'civicrm_premiums_product.weight', 'table_name' => 'civicrm_premiums_product', 'entity' => 'PremiumsProduct', 'bao' => 'CRM_Contribute_DAO_PremiumsProduct', 'localizable' => 0, - ) , - 'financial_type_id' => array( + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'FK to Financial Type.', + 'title' => ts('Financial Type'), + 'description' => ts('FK to Financial Type.'), + 'where' => 'civicrm_premiums_product.financial_type_id', 'default' => 'NULL', 'table_name' => 'civicrm_premiums_product', 'entity' => 'PremiumsProduct', 'bao' => 'CRM_Contribute_DAO_PremiumsProduct', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - ); + ], + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -212,10 +205,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'premiums_product', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'premiums_product', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -223,15 +217,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'premiums_product', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'premiums_product', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/Product.php b/CRM/Contribute/DAO/Product.php index fcfdbc7b39c2..398b0ba375bb 100644 --- a/CRM/Contribute/DAO/Product.php +++ b/CRM/Contribute/DAO/Product.php @@ -1,497 +1,512 @@ we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) + * (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) * * @var string */ public $period_type; + /** * Month and day (MMDD) that fixed period type subscription or membership starts. * * @var int */ public $fixed_period_start_day; + /** - * * @var string */ public $duration_unit; + /** * Number of units for total duration of subscription, service, membership (e.g. 12 Months). * * @var int */ public $duration_interval; + /** * Frequency unit and interval allow option to store actual delivery frequency for a subscription or service. * * @var string */ public $frequency_unit; + /** * Number of units for delivery frequency of subscription, service, membership (e.g. every 3 Months). * * @var int */ public $frequency_interval; + /** * Class constructor. */ - function __construct() { + public function __construct() { $this->__table = 'civicrm_product'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'financial_type_id', 'civicrm_financial_type', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'financial_type_id', 'civicrm_financial_type', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Product ID') , - 'required' => true, + 'title' => ts('Product ID'), + 'required' => TRUE, + 'where' => 'civicrm_product.id', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'product_name' => array( + ], + 'product_name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Product Name') , - 'description' => 'Required product/premium name', - 'required' => true, + 'title' => ts('Product Name'), + 'description' => ts('Required product/premium name'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'export' => true, 'where' => 'civicrm_product.name', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 1, - ) , - 'description' => array( + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Description') , - 'description' => 'Optional description of the product/premium.', + 'title' => ts('Description'), + 'description' => ts('Optional description of the product/premium.'), + 'where' => 'civicrm_product.description', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 1, - ) , - 'sku' => array( + ], + 'sku' => [ 'name' => 'sku', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('SKU') , - 'description' => 'Optional product sku or code.', + 'title' => ts('SKU'), + 'description' => ts('Optional product sku or code.'), 'maxlength' => 50, 'size' => CRM_Utils_Type::BIG, - 'export' => true, 'where' => 'civicrm_product.sku', - 'headerPattern' => '', - 'dataPattern' => '', + 'export' => TRUE, 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'options' => array( + ], + 'options' => [ 'name' => 'options', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Options') , - 'description' => 'Store comma-delimited list of color, size, etc. options for the product.', + 'title' => ts('Options'), + 'description' => ts('Store comma-delimited list of color, size, etc. options for the product.'), + 'where' => 'civicrm_product.options', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 1, - ) , - 'image' => array( + ], + 'image' => [ 'name' => 'image', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Image') , - 'description' => 'Full or relative URL to uploaded image - fullsize.', + 'title' => ts('Image'), + 'description' => ts('Full or relative URL to uploaded image - fullsize.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_product.image', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'thumbnail' => array( + ], + 'thumbnail' => [ 'name' => 'thumbnail', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Thumbnail') , - 'description' => 'Full or relative URL to image thumbnail.', + 'title' => ts('Thumbnail'), + 'description' => ts('Full or relative URL to image thumbnail.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_product.thumbnail', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'price' => array( + ], + 'price' => [ 'name' => 'price', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Price') , - 'description' => 'Sell price or market value for premiums. For tax-deductible contributions, this will be stored as non_deductible_amount in the contribution record.', - 'precision' => array( + 'title' => ts('Price'), + 'description' => ts('Sell price or market value for premiums. For tax-deductible contributions, this will be stored as non_deductible_amount in the contribution record.'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_product.price', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'currency' => array( + ], + 'currency' => [ 'name' => 'currency', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Currency') , - 'description' => '3 character string, value from config setting or input via user.', + 'title' => ts('Currency'), + 'description' => ts('3 character string, value from config setting or input via user.'), 'maxlength' => 3, 'size' => CRM_Utils_Type::FOUR, + 'where' => 'civicrm_product.currency', 'default' => 'NULL', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'table' => 'civicrm_currency', 'keyColumn' => 'name', 'labelColumn' => 'full_name', 'nameColumn' => 'name', - ) - ) , - 'financial_type_id' => array( + 'abbrColumn' => 'symbol', + ], + ], + 'financial_type_id' => [ 'name' => 'financial_type_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Financial Type') , - 'description' => 'FK to Financial Type.', + 'title' => ts('Financial Type'), + 'description' => ts('FK to Financial Type.'), + 'where' => 'civicrm_product.financial_type_id', 'default' => 'NULL', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, 'FKClassName' => 'CRM_Financial_DAO_FinancialType', - 'pseudoconstant' => array( + 'pseudoconstant' => [ 'table' => 'civicrm_financial_type', 'keyColumn' => 'id', 'labelColumn' => 'name', - ) - ) , - 'min_contribution' => array( + ], + ], + 'min_contribution' => [ 'name' => 'min_contribution', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Minimum Contribution') , - 'description' => 'Minimum contribution required to be eligible to select this premium.', - 'precision' => array( + 'title' => ts('Minimum Contribution'), + 'description' => ts('Minimum contribution required to be eligible to select this premium.'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_product.min_contribution', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'cost' => array( + ], + 'cost' => [ 'name' => 'cost', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => ts('Cost') , - 'description' => 'Actual cost of this product. Useful to determine net return from sale or using this as an incentive.', - 'precision' => array( + 'title' => ts('Cost'), + 'description' => ts('Actual cost of this product. Useful to determine net return from sale or using this as an incentive.'), + 'precision' => [ 20, - 2 - ) , + 2, + ], + 'where' => 'civicrm_product.cost', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Is Active') , - 'description' => 'Disabling premium removes it from the premiums_premium join table below.', - 'required' => true, + 'title' => ts('Is Active'), + 'description' => ts('Disabling premium removes it from the premiums_premium join table below.'), + 'required' => TRUE, + 'where' => 'civicrm_product.is_active', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'period_type' => array( + ], + 'period_type' => [ 'name' => 'period_type', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Period Type') , - 'description' => 'Rolling means we set start/end based on current day, fixed means we set start/end for current year or month - (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) ', + 'title' => ts('Period Type'), + 'description' => ts('Rolling means we set start/end based on current day, fixed means we set start/end for current year or month + (e.g. 1 year + fixed -> we would set start/end for 1/1/06 thru 12/31/06 for any premium chosen in 2006) '), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_product.period_type', 'default' => 'rolling', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::periodType', - ) - ) , - 'fixed_period_start_day' => array( + ], + ], + 'fixed_period_start_day' => [ 'name' => 'fixed_period_start_day', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Fixed Period Start Day') , - 'description' => 'Month and day (MMDD) that fixed period type subscription or membership starts.', + 'title' => ts('Fixed Period Start Day'), + 'description' => ts('Month and day (MMDD) that fixed period type subscription or membership starts.'), + 'where' => 'civicrm_product.fixed_period_start_day', 'default' => '0101', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'duration_unit' => array( + ], + 'duration_unit' => [ 'name' => 'duration_unit', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Duration Unit') , + 'title' => ts('Duration Unit'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_product.duration_unit', 'default' => 'year', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::getPremiumUnits', - ) - ) , - 'duration_interval' => array( + ], + ], + 'duration_interval' => [ 'name' => 'duration_interval', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Duration Interval') , - 'description' => 'Number of units for total duration of subscription, service, membership (e.g. 12 Months).', + 'title' => ts('Duration Interval'), + 'description' => ts('Number of units for total duration of subscription, service, membership (e.g. 12 Months).'), + 'where' => 'civicrm_product.duration_interval', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - 'frequency_unit' => array( + ], + 'frequency_unit' => [ 'name' => 'frequency_unit', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Frequency Unit') , - 'description' => 'Frequency unit and interval allow option to store actual delivery frequency for a subscription or service.', + 'title' => ts('Frequency Unit'), + 'description' => ts('Frequency unit and interval allow option to store actual delivery frequency for a subscription or service.'), 'maxlength' => 8, 'size' => CRM_Utils_Type::EIGHT, + 'where' => 'civicrm_product.frequency_unit', 'default' => 'month', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - 'html' => array( + 'html' => [ 'type' => 'Select', - ) , - 'pseudoconstant' => array( + ], + 'pseudoconstant' => [ 'callback' => 'CRM_Core_SelectValues::getPremiumUnits', - ) - ) , - 'frequency_interval' => array( + ], + ], + 'frequency_interval' => [ 'name' => 'frequency_interval', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Frequency Interval') , - 'description' => 'Number of units for delivery frequency of subscription, service, membership (e.g. every 3 Months).', + 'title' => ts('Frequency Interval'), + 'description' => ts('Number of units for delivery frequency of subscription, service, membership (e.g. every 3 Months).'), + 'where' => 'civicrm_product.frequency_interval', 'table_name' => 'civicrm_product', 'entity' => 'Product', - 'bao' => 'CRM_Contribute_DAO_Product', + 'bao' => 'CRM_Contribute_BAO_Product', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return CRM_Core_DAO::getLocaleTableName(self::$_tableName); } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -499,10 +514,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'product', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'product', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -510,15 +526,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'product', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'product', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/DAO/Widget.php b/CRM/Contribute/DAO/Widget.php index 3c57207f2efd..964ff7ed65b7 100644 --- a/CRM/Contribute/DAO/Widget.php +++ b/CRM/Contribute/DAO/Widget.php @@ -1,399 +1,408 @@ __table = 'civicrm_contribution_widget'; parent::__construct(); } + /** * Returns foreign keys and entity references. * * @return array * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() { + public static function getReferenceColumns() { if (!isset(Civi::$statics[__CLASS__]['links'])) { - Civi::$statics[__CLASS__]['links'] = static ::createReferenceColumns(__CLASS__); - Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName() , 'contribution_page_id', 'civicrm_contribution_page', 'id'); + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_page_id', 'civicrm_contribution_page', 'id'); CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } return Civi::$statics[__CLASS__]['links']; } + /** * Returns all the column names of this table * * @return array */ - static function &fields() { + public static function &fields() { if (!isset(Civi::$statics[__CLASS__]['fields'])) { - Civi::$statics[__CLASS__]['fields'] = array( - 'id' => array( + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Widget ID') , - 'description' => 'Contribution Id', - 'required' => true, + 'title' => ts('Widget ID'), + 'description' => ts('Contribution Id'), + 'required' => TRUE, + 'where' => 'civicrm_contribution_widget.id', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'contribution_page_id' => array( + ], + 'contribution_page_id' => [ 'name' => 'contribution_page_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => ts('Contribution Page') , - 'description' => 'The Contribution Page which triggered this contribution', + 'title' => ts('Contribution Page'), + 'description' => ts('The Contribution Page which triggered this contribution'), + 'where' => 'civicrm_contribution_widget.contribution_page_id', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_ContributionPage', - ) , - 'is_active' => array( + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => ts('Enabled?') , - 'description' => 'Is this property active?', + 'title' => ts('Enabled?'), + 'description' => ts('Is this property active?'), + 'where' => 'civicrm_contribution_widget.is_active', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'title' => array( + ], + 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Widget Title') , - 'description' => 'Widget title.', + 'title' => ts('Widget Title'), + 'description' => ts('Widget title.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_widget.title', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'url_logo' => array( + ], + 'url_logo' => [ 'name' => 'url_logo', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Widget Image Url') , - 'description' => 'URL to Widget logo', + 'title' => ts('Widget Image Url'), + 'description' => ts('URL to Widget logo'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_widget.url_logo', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'button_title' => array( + ], + 'button_title' => [ 'name' => 'button_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Button Title') , - 'description' => 'Button title.', + 'title' => ts('Button Title'), + 'description' => ts('Button title.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_widget.button_title', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'about' => array( + ], + 'about' => [ 'name' => 'about', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => ts('Description') , - 'description' => 'About description.', + 'title' => ts('Description'), + 'description' => ts('About description.'), + 'where' => 'civicrm_contribution_widget.about', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'url_homepage' => array( + ], + 'url_homepage' => [ 'name' => 'url_homepage', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Homepage Url') , - 'description' => 'URL to Homepage.', + 'title' => ts('Homepage Url'), + 'description' => ts('URL to Homepage.'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, + 'where' => 'civicrm_contribution_widget.url_homepage', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_title' => array( + ], + 'color_title' => [ 'name' => 'color_title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Title Color') , + 'title' => ts('Title Color'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_title', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_button' => array( + ], + 'color_button' => [ 'name' => 'color_button', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Button Colour') , + 'title' => ts('Button Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_button', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_bar' => array( + ], + 'color_bar' => [ 'name' => 'color_bar', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Bar Color') , + 'title' => ts('Bar Color'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_bar', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_main_text' => array( + ], + 'color_main_text' => [ 'name' => 'color_main_text', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Main Text Color') , + 'title' => ts('Main Text Color'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_main_text', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_main' => array( + ], + 'color_main' => [ 'name' => 'color_main', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Main Colour') , + 'title' => ts('Main Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_main', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_main_bg' => array( + ], + 'color_main_bg' => [ 'name' => 'color_main_bg', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Backgroup Color') , + 'title' => ts('Backgroup Color'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_main_bg', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_bg' => array( + ], + 'color_bg' => [ 'name' => 'color_bg', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Other Backgroun Colour') , + 'title' => ts('Other Backgroun Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_bg', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_about_link' => array( + ], + 'color_about_link' => [ 'name' => 'color_about_link', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('About Link Colour') , + 'title' => ts('About Link Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_about_link', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - 'color_homepage_link' => array( + ], + 'color_homepage_link' => [ 'name' => 'color_homepage_link', 'type' => CRM_Utils_Type::T_STRING, - 'title' => ts('Homepage Link Colour') , + 'title' => ts('Homepage Link Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, + 'where' => 'civicrm_contribution_widget.color_homepage_link', 'table_name' => 'civicrm_contribution_widget', 'entity' => 'Widget', 'bao' => 'CRM_Contribute_BAO_Widget', 'localizable' => 0, - ) , - ); + ], + ]; CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } return Civi::$statics[__CLASS__]['fields']; } + /** * Return a mapping from field-name to the corresponding key (as used in fields()). * * @return array * Array(string $name => string $uniqueName). */ - static function &fieldKeys() { + public static function &fieldKeys() { if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } return Civi::$statics[__CLASS__]['fieldKeys']; } + /** * Returns the names of this table * * @return string */ - static function getTableName() { + public static function getTableName() { return self::$_tableName; } + /** * Returns if this table needs to be logged * - * @return boolean + * @return bool */ - function getLog() { + public function getLog() { return self::$_log; } + /** * Returns the list of fields that can be imported * @@ -401,10 +410,11 @@ function getLog() { * * @return array */ - static function &import($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_widget', $prefix, array()); + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'contribution_widget', $prefix, []); return $r; } + /** * Returns the list of fields that can be exported * @@ -412,15 +422,21 @@ static function &import($prefix = false) { * * @return array */ - static function &export($prefix = false) { - $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_widget', $prefix, array()); + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'contribution_widget', $prefix, []); return $r; } + /** * Returns the list of indices + * + * @param bool $localize + * + * @return array */ public static function indices($localize = TRUE) { - $indices = array(); + $indices = []; return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Contribute/Exception/CheckLineItemsException.php b/CRM/Contribute/Exception/CheckLineItemsException.php new file mode 100644 index 000000000000..c1c0d3a91427 --- /dev/null +++ b/CRM/Contribute/Exception/CheckLineItemsException.php @@ -0,0 +1,13 @@ +id = $id; + CRM_Core_Error::debug_log_message('Access to contribution page with start date in future attempted - page number ' . $id); + } + + /** + * Get Contribution page ID. + * + * @return int + */ + public function getID() { + return $this->id; + } + +} diff --git a/CRM/Contribute/Exception/InactiveContributionPageException.php b/CRM/Contribute/Exception/InactiveContributionPageException.php index 5c8b82e65f01..828a5c5798e0 100644 --- a/CRM/Contribute/Exception/InactiveContributionPageException.php +++ b/CRM/Contribute/Exception/InactiveContributionPageException.php @@ -14,7 +14,7 @@ class CRM_Contribute_Exception_InactiveContributionPageException extends Excepti * @param int $id */ public function __construct($message, $id) { - parent::__construct(ts($message)); + parent::__construct($message); $this->id = $id; CRM_Core_Error::debug_log_message('inactive contribution page access attempted - page number ' . $id); } diff --git a/CRM/Contribute/Exception/PastContributionPageException.php b/CRM/Contribute/Exception/PastContributionPageException.php new file mode 100644 index 000000000000..71c825a7ab49 --- /dev/null +++ b/CRM/Contribute/Exception/PastContributionPageException.php @@ -0,0 +1,25 @@ +id = $id; + CRM_Core_Error::debug_log_message('Access to contribution page with past end date attempted - page number ' . $id); + } + + /** + * Get Contribution page ID. + * + * @return int + */ + public function getID() { + return $this->id; + } + +} diff --git a/CRM/Contribute/Form.php b/CRM/Contribute/Form.php index 6c1b8a52af04..745b0d7c66cb 100644 --- a/CRM/Contribute/Form.php +++ b/CRM/Contribute/Form.php @@ -1,9 +1,9 @@ _id)) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; if (!empty($this->_BAOName)) { $baoName = $this->_BAOName; $baoName::retrieve($params, $defaults); diff --git a/CRM/Contribute/Form/AbstractEditPayment.php b/CRM/Contribute/Form/AbstractEditPayment.php index 241638a015a5..bc37b6f8a9be 100644 --- a/CRM/Contribute/Form/AbstractEditPayment.php +++ b/CRM/Contribute/Form/AbstractEditPayment.php @@ -1,9 +1,9 @@ array($id => $label) @@ -79,7 +84,7 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { * Available payment processors with full details including the key 'object' indexed by their id * @var array */ - protected $_paymentProcessors = array(); + protected $_paymentProcessors = []; /** * Instance of the payment processor object. @@ -95,6 +100,15 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { */ public $_id; + /** + * Entity that $this->_id relates to. + * + * If set the contact id is not required in the url. + * + * @var string + */ + protected $entity; + /** * The id of the premium that we are proceessing. * @@ -139,7 +153,7 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { * Is this contribution associated with an online * financial transaction * - * @var boolean + * @var bool */ public $_online = FALSE; @@ -166,11 +180,13 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { /** * The contribution values if an existing contribution + * @var array */ public $_values; /** * The pledge values if this contribution is associated with pledge + * @var array */ public $_pledgeValues; @@ -182,6 +198,7 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { /** * Store the line items if price set used. + * @var array */ public $_lineItems; @@ -201,6 +218,13 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { */ public $paymentInstrumentID; + /** + * Component - event, membership or contribution. + * + * @var string + */ + protected $_component; + /** * Array of fields to display on billingBlock.tpl - this is not fully implemented but basically intent is the panes/fieldsets on this page should * be all in this array in order like @@ -210,18 +234,32 @@ class CRM_Contribute_Form_AbstractEditPayment extends CRM_Contact_Form_Task { * such that both the fields and the order can be more easily altered by payment processors & other extensions * @var array */ - public $billingFieldSets = array(); + public $billingFieldSets = []; + + /** + * Monetary fields that may be submitted. + * + * These should get a standardised format in the beginPostProcess function. + * + * These fields are common to many forms. Some may override this. + * @var array + */ + protected $submittableMoneyFields = ['total_amount', 'net_amount', 'non_deductible_amount', 'fee_amount']; /** * Pre process function with common actions. */ public function preProcess() { $this->_contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this); + if (empty($this->_contactID) && !empty($this->_id) && $this->entity) { + $this->_contactID = civicrm_api3($this->entity, 'getvalue', ['id' => $this->_id, 'return' => 'contact_id']); + } $this->assign('contactID', $this->_contactID); - CRM_Core_Resources::singleton()->addVars('coreForm', array('contact_id' => (int) $this->_contactID)); + CRM_Core_Resources::singleton()->addVars('coreForm', ['contact_id' => (int) $this->_contactID]); $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add'); - $this->_mode = empty($this->_mode) ? CRM_Utils_Request::retrieve('mode', 'String', $this) : $this->_mode; + $this->_mode = empty($this->_mode) ? CRM_Utils_Request::retrieve('mode', 'Alphanumeric', $this) : $this->_mode; $this->assign('isBackOffice', $this->isBackOffice); + $this->assignContactEmailDetails(); $this->assignPaymentRelatedVariables(); } @@ -235,7 +273,7 @@ public function showRecordLinkMesssage($id) { $recordPaymentLink = CRM_Utils_System::url('civicrm/payment', "reset=1&id={$pid}&cid={$this->_contactID}&action=add&component=event" ); - CRM_Core_Session::setStatus(ts('Please use the Record Payment form if you have received an additional payment for this Partially paid contribution record.', array(1 => $recordPaymentLink)), ts('Notice'), 'alert'); + CRM_Core_Session::setStatus(ts('Please use the Record Payment form if you have received an additional payment for this Partially paid contribution record.', [1 => $recordPaymentLink]), ts('Notice'), 'alert'); } } } @@ -245,8 +283,8 @@ public function showRecordLinkMesssage($id) { * @param $values */ public function buildValuesAndAssignOnline_Note_Type($id, &$values) { - $ids = array(); - $params = array('id' => $id); + $ids = []; + $params = ['id' => $id]; CRM_Contribute_BAO_Contribution::getValues($params, $values, $ids); //Check if this is an online transaction (financial_trxn.payment_processor_id NOT NULL) @@ -300,14 +338,11 @@ public function assignPremiumProduct($id) { FROM civicrm_contribution_product WHERE contribution_id = {$id} "; - $dao = CRM_Core_DAO::executeQuery($sql, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($sql); if ($dao->fetch()) { $this->_premiumID = $dao->id; $this->_productDAO = $dao; } - $dao->free(); } /** @@ -316,7 +351,7 @@ public function assignPremiumProduct($id) { * @throws Exception */ public function getValidProcessors() { - $capabilities = array('BackOffice'); + $capabilities = ['BackOffice']; if ($this->_mode) { $capabilities[] = (ucfirst($this->_mode) . 'Mode'); } @@ -331,7 +366,7 @@ public function getValidProcessors() { public function assignProcessors() { //ensure that processor has a valid config //only valid processors get display to user - $this->assign('processorSupportsFutureStartDate', CRM_Financial_BAO_PaymentProcessor::hasPaymentProcessorSupporting(array('FutureRecurStartDate'))); + $this->assign('processorSupportsFutureStartDate', CRM_Financial_BAO_PaymentProcessor::hasPaymentProcessorSupporting(['FutureRecurStartDate'])); $this->_paymentProcessors = $this->getValidProcessors(); if (!isset($this->_paymentProcessor['id'])) { // if the payment processor isn't set yet (as indicated by the presence of an id,) we'll grab the first one which should be the default @@ -340,10 +375,15 @@ public function assignProcessors() { if (!$this->_mode) { $this->_paymentProcessor = $this->_paymentProcessors[0]; } - elseif (empty($this->_paymentProcessors) || array_keys($this->_paymentProcessors) === array(0)) { - throw new CRM_Core_Exception(ts('You will need to configure the %1 settings for your Payment Processor before you can submit a credit card transactions.', array(1 => $this->_mode))); + elseif (empty($this->_paymentProcessors) || array_keys($this->_paymentProcessors) === [0]) { + throw new CRM_Core_Exception(ts('You will need to configure the %1 settings for your Payment Processor before you can submit a credit card transactions.', [1 => $this->_mode])); + } + //Assign submitted processor value if it is different from the loaded one. + if (!empty($this->_submitValues['payment_processor_id']) + && $this->_paymentProcessor['id'] != $this->_submitValues['payment_processor_id']) { + $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($this->_submitValues['payment_processor_id']); } - $this->_processors = array(); + $this->_processors = []; foreach ($this->_paymentProcessors as $id => $processor) { // @todo review this. The inclusion of this IF was to address test processors being incorrectly loaded. // However the function $this->getValidProcessors() is expected to only return the processors relevant @@ -351,16 +391,17 @@ public function assignProcessors() { // for some reason there was a need to filter here per commit history - but this indicates a problem // somewhere else. if ($processor['is_test'] == ($this->_mode == 'test')) { - $this->_processors[$id] = ts($processor['name']); + $this->_processors[$id] = $processor['name']; if (!empty($processor['description'])) { - $this->_processors[$id] .= ' : ' . ts($processor['description']); + $this->_processors[$id] .= ' : ' . $processor['description']; } if ($processor['is_recur']) { $this->_recurPaymentProcessors[$id] = $this->_processors[$id]; } } } - CRM_Financial_Form_Payment::addCreditCardJs($id); + // CRM-21002: pass the default payment processor ID whose credit card type icons should be populated first + CRM_Financial_Form_Payment::addCreditCardJs($this->_paymentProcessor['id']); $this->assign('recurringPaymentProcessorIds', empty($this->_recurPaymentProcessors) ? '' : implode(',', array_keys($this->_recurPaymentProcessors)) @@ -368,17 +409,17 @@ public function assignProcessors() { // this required to show billing block // @todo remove this assignment the billing block is now designed to be always included but will not show fieldsets unless those sets of fields are assigned - $this->assign_by_ref('paymentProcessor', $processor); + $this->assign_by_ref('paymentProcessor', $this->_paymentProcessor); } /** * Get current currency from DB or use default currency. * - * @param $submittedValues + * @param array $submittedValues * - * @return mixed + * @return string */ - public function getCurrency($submittedValues) { + public function getCurrency($submittedValues = []) { $config = CRM_Core_Config::singleton(); $currentCurrency = CRM_Utils_Array::value('currency', @@ -396,9 +437,9 @@ public function getCurrency($submittedValues) { public function preProcessPledge() { //get the payment values associated with given pledge payment id OR check for payments due. - $this->_pledgeValues = array(); + $this->_pledgeValues = []; if ($this->_ppID) { - $payParams = array('id' => $this->_ppID); + $payParams = ['id' => $this->_ppID]; CRM_Pledge_BAO_PledgePayment::retrieve($payParams, $this->_pledgeValues['pledgePayment']); $this->_pledgeID = CRM_Utils_Array::value('pledge_id', $this->_pledgeValues['pledgePayment']); @@ -413,8 +454,8 @@ public function preProcessPledge() { //get the pledge values associated with given pledge payment. - $ids = array(); - $pledgeParams = array('id' => $this->_pledgeID); + $ids = []; + $pledgeParams = ['id' => $this->_pledgeID]; CRM_Pledge_BAO_Pledge::getValues($pledgeParams, $this->_pledgeValues, $ids); $this->assign('ppID', $this->_ppID); } @@ -443,7 +484,7 @@ public function preProcessPledge() { $pledgeTab = CRM_Utils_System::url('civicrm/contact/view', "reset=1&force=1&cid={$this->_contactID}&selectedChild=pledge" ); - CRM_Core_Session::setStatus(ts('This contact has pending or overdue pledge payments. Click here to view their Pledges tab and verify whether this contribution should be applied as a pledge payment.', array(1 => $pledgeTab)), ts('Notice'), 'alert'); + CRM_Core_Session::setStatus(ts('This contact has pending or overdue pledge payments. Click here to view their Pledges tab and verify whether this contribution should be applied as a pledge payment.', [1 => $pledgeTab]), ts('Notice'), 'alert'); } elseif ($paymentsDue) { // Show user link to oldest Pending or Overdue pledge payment @@ -459,11 +500,11 @@ public function preProcessPledge() { "reset=1&action=add&cid={$this->_contactID}&ppid={$payments['id']}&context=pledge" ); } - CRM_Core_Session::setStatus(ts('This contact has a pending or overdue pledge payment of %2 which is scheduled for %3. Click here to enter a pledge payment.', array( + CRM_Core_Session::setStatus(ts('This contact has a pending or overdue pledge payment of %2 which is scheduled for %3. Click here to enter a pledge payment.', [ 1 => $ppUrl, 2 => $ppAmountDue, 3 => $ppSchedDate, - )), ts('Notice'), 'alert'); + ]), ts('Notice'), 'alert'); } } } @@ -477,7 +518,7 @@ public function preProcessPledge() { */ public function unsetCreditCardFields($submittedValues) { //Offline Contribution. - $unsetParams = array( + $unsetParams = [ 'payment_processor_id', "email-{$this->_bltID}", 'hidden_buildCreditCard', @@ -494,7 +535,7 @@ public function unsetCreditCardFields($submittedValues) { 'cvv2', 'credit_card_exp_date', 'credit_card_type', - ); + ]; foreach ($unsetParams as $key) { if (isset($submittedValues[$key])) { unset($submittedValues[$key]); @@ -509,16 +550,12 @@ public function unsetCreditCardFields($submittedValues) { */ protected function assignPaymentRelatedVariables() { try { - if ($this->_contactID) { - list($this->userDisplayName, $this->userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID); - $this->assign('displayName', $this->userDisplayName); - } $this->assignProcessors(); $this->assignBillingType(); - CRM_Core_Payment_Form::setPaymentFieldsByProcessor($this, $this->_paymentProcessor, FALSE, TRUE, CRM_Utils_Request::retrieve('payment_instrument_id', 'Integer')); + CRM_Core_Payment_Form::setPaymentFieldsByProcessor($this, $this->_paymentProcessor, FALSE, TRUE, CRM_Utils_Request::retrieve('payment_instrument_id', 'Integer', $this)); } catch (CRM_Core_Exception $e) { - CRM_Core_Error::fatal($e->getMessage()); + CRM_Core_Error::statusBounce($e->getMessage()); } } @@ -541,14 +578,22 @@ protected function beginPostProcess() { $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params); } $this->assign('credit_card_exp_date', CRM_Utils_Date::mysqlToIso(CRM_Utils_Date::format($this->_params['credit_card_exp_date']))); - $this->assign('credit_card_number', - CRM_Utils_System::mungeCreditCard($this->_params['credit_card_number']) - ); + $this->assign('credit_card_number', CRM_Utils_System::mungeCreditCard($this->_params['credit_card_number'])); $this->assign('credit_card_type', CRM_Utils_Array::value('credit_card_type', $this->_params)); } $this->_params['ip_address'] = CRM_Utils_System::ipAddress(); self::formatCreditCardDetails($this->_params); + foreach ($this->submittableMoneyFields as $moneyField) { + if (isset($this->_params[$moneyField])) { + $this->_params[$moneyField] = CRM_Utils_Rule::cleanMoney($this->_params[$moneyField]); + } + } + if (!empty($this->_params['contact_id']) && empty($this->_contactID)) { + // Contact ID has been set in the standalone form. + $this->_contactID = $this->_params['contact_id']; + $this->assignContactEmailDetails(); + } } /** @@ -561,7 +606,7 @@ protected function beginPostProcess() { * @return void */ public static function formatCreditCardDetails(&$params) { - if (in_array('credit_card_type', array_keys($params))) { + if (!empty($params['credit_card_type'])) { $params['card_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', $params['credit_card_type']); } if (!empty($params['credit_card_number']) && empty($params['pan_truncation'])) { @@ -577,7 +622,7 @@ public static function formatCreditCardDetails(&$params) { * for pay later. */ protected function processBillingAddress() { - $fields = array(); + $fields = []; $fields['email-Primary'] = 1; $this->_params['email-5'] = $this->_params['email-Primary'] = $this->_contributorEmail; @@ -654,4 +699,65 @@ protected function getDefaultPaymentInstrumentId() { return key(CRM_Core_OptionGroup::values('payment_instrument', FALSE, FALSE, FALSE, 'AND is_default = 1')); } + /** + * Add the payment processor select to the form. + * + * @param bool $isRequired + * Is it a mandatory field. + * @param bool $isBuildRecurBlock + * True if we want to build recur on change + * @param bool $isBuildAutoRenewBlock + * True if we want to build autorenew on change. + */ + protected function addPaymentProcessorSelect($isRequired, $isBuildRecurBlock = FALSE, $isBuildAutoRenewBlock = FALSE) { + if (!$this->_mode) { + return; + } + $js = ($isBuildRecurBlock ? ['onChange' => "buildRecurBlock( this.value ); return false;"] : NULL); + if ($isBuildAutoRenewBlock) { + $js = ['onChange' => "buildAutoRenew( null, this.value, '{$this->_mode}');"]; + } + $element = $this->add('select', + 'payment_processor_id', + ts('Payment Processor'), + array_diff_key($this->_processors, [0 => 1]), + $isRequired, + $js + ); + // The concept of _online is not really explained & the code is old + // @todo figure out & document. + if ($this->_online) { + $element->freeze(); + } + } + + /** + * Assign the values to build the payment info block. + * + * @return string + * Block title. + */ + protected function assignPaymentInfoBlock() { + $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_id, $this->_component, TRUE); + $title = ts('View Payment'); + if (!empty($this->_component) && $this->_component == 'event') { + $info = CRM_Event_BAO_Participant::participantDetails($this->_id); + $title .= " - {$info['title']}"; + } + $this->assign('transaction', TRUE); + $this->assign('payments', $paymentInfo['transaction']); + $this->assign('paymentLinks', $paymentInfo['payment_links']); + return $title; + } + + protected function assignContactEmailDetails() { + if ($this->_contactID) { + list($this->userDisplayName, $this->userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID); + if (empty($this->userDisplayName)) { + $this->userDisplayName = civicrm_api3('contact', 'getvalue', ['id' => $this->_contactID, 'return' => 'display_name']); + } + $this->assign('displayName', $this->userDisplayName); + } + } + } diff --git a/CRM/Contribute/Form/AdditionalInfo.php b/CRM/Contribute/Form/AdditionalInfo.php index 9498393a50b8..eb68ed514c4c 100644 --- a/CRM/Contribute/Form/AdditionalInfo.php +++ b/CRM/Contribute/Form/AdditionalInfo.php @@ -1,9 +1,9 @@ add('hidden', 'hidden_Premium', 1); - $sel1 = $sel2 = array(); + $sel1 = $sel2 = []; $dao = new CRM_Contribute_DAO_Product(); $dao->is_active = 1; $dao->find(); - $min_amount = array(); + $min_amount = []; $sel1[0] = ts('-select product-'); while ($dao->fetch()) { $sel1[$dao->id] = $dao->name . " ( " . $dao->sku . " )"; @@ -77,11 +77,11 @@ public static function buildPremium(&$form) { } } - $sel->setOptions(array($sel1, $sel2)); + $sel->setOptions([$sel1, $sel2]); $js .= "\n"; $form->assign('initHideBoxes', $js); - $form->addDate('fulfilled_date', ts('Fulfilled'), FALSE, array('formatType' => 'activityDate')); + $form->add('datepicker', 'fulfilled_date', ts('Fulfilled'), [], FALSE, ['time' => FALSE]); $form->addElement('text', 'min_amount', ts('Minimum Contribution Amount')); } @@ -96,7 +96,7 @@ public static function buildAdditionalDetail(&$form) { $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Contribution'); - $form->addDateTime('thankyou_date', ts('Thank-you Sent'), FALSE, array('formatType' => 'activityDateTime')); + $form->addField('thankyou_date', ['entity' => 'contribution'], FALSE, FALSE); // add various amounts $nonDeductAmount = &$form->add('text', 'non_deductible_amount', ts('Non-deductible Amount'), @@ -115,13 +115,6 @@ public static function buildAdditionalDetail(&$form) { $feeAmount->freeze(); } - $netAmount = &$form->add('text', 'net_amount', ts('Net Amount'), - $attributes['net_amount'] - ); - $form->addRule('net_amount', ts('Please enter a valid monetary value for Net Amount.'), 'money'); - if ($form->_online) { - $netAmount->freeze(); - } $element = &$form->add('text', 'invoice_id', ts('Invoice ID'), $attributes['invoice_id'] ); @@ -132,7 +125,7 @@ public static function buildAdditionalDetail(&$form) { $form->addRule('invoice_id', ts('This Invoice ID already exists in the database.'), 'objectExists', - array('CRM_Contribute_DAO_Contribution', $form->_id, 'invoice_id') + ['CRM_Contribute_DAO_Contribution', $form->_id, 'invoice_id'] ); } $element = $form->add('text', 'creditnote_id', ts('Credit Note ID'), @@ -145,23 +138,22 @@ public static function buildAdditionalDetail(&$form) { $form->addRule('creditnote_id', ts('This Credit Note ID already exists in the database.'), 'objectExists', - array('CRM_Contribute_DAO_Contribution', $form->_id, 'creditnote_id') + ['CRM_Contribute_DAO_Contribution', $form->_id, 'creditnote_id'] ); } $form->add('select', 'contribution_page_id', ts('Online Contribution Page'), - array( + [ '' => ts('- select -'), - ) + + ] + CRM_Contribute_PseudoConstant::contributionPage() ); - $form->add('textarea', 'note', ts('Notes'), array("rows" => 4, "cols" => 60)); + $form->add('textarea', 'note', ts('Notes'), ["rows" => 4, "cols" => 60]); $statusName = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); if ($form->_id && $form->_values['contribution_status_id'] == array_search('Cancelled', $statusName)) { - $netAmount->freeze(); $feeAmount->freeze(); } @@ -177,11 +169,11 @@ public static function buildAdditionalDetail(&$form) { public static function buildPaymentReminders(&$form) { //PaymentReminders section $form->add('hidden', 'hidden_PaymentReminders', 1); - $form->add('text', 'initial_reminder_day', ts('Send Initial Reminder'), array('size' => 3)); + $form->add('text', 'initial_reminder_day', ts('Send Initial Reminder'), ['size' => 3]); $form->addRule('initial_reminder_day', ts('Please enter a valid reminder day.'), 'positiveInteger'); - $form->add('text', 'max_reminders', ts('Send up to'), array('size' => 3)); + $form->add('text', 'max_reminders', ts('Send up to'), ['size' => 3]); $form->addRule('max_reminders', ts('Please enter a valid No. of reminders.'), 'positiveInteger'); - $form->add('text', 'additional_reminder_day', ts('Send additional reminders'), array('size' => 3)); + $form->add('text', 'additional_reminder_day', ts('Send additional reminders'), ['size' => 3]); $form->addRule('additional_reminder_day', ts('Please enter a valid additional reminder day.'), 'positiveInteger'); } @@ -193,23 +185,23 @@ public static function buildPaymentReminders(&$form) { * @param int $premiumID * @param array $options */ - public static function processPremium($params, $contributionID, $premiumID = NULL, $options = array()) { + public static function processPremium($params, $contributionID, $premiumID = NULL, $options = []) { $selectedProductID = $params['product_name'][0]; $selectedProductOptionID = CRM_Utils_Array::value(1, $params['product_name']); $dao = new CRM_Contribute_DAO_ContributionProduct(); $dao->contribution_id = $contributionID; $dao->product_id = $selectedProductID; - $dao->fulfilled_date = CRM_Utils_Date::processDate($params['fulfilled_date'], NULL, TRUE); + $dao->fulfilled_date = $params['fulfilled_date']; $isDeleted = FALSE; //CRM-11106 - $premiumParams = array( + $premiumParams = [ 'id' => $selectedProductID, - ); + ]; - $productDetails = array(); - CRM_Contribute_BAO_ManagePremiums::retrieve($premiumParams, $productDetails); + $productDetails = []; + CRM_Contribute_BAO_Product::retrieve($premiumParams, $productDetails); $dao->financial_type_id = CRM_Utils_Array::value('financial_type_id', $productDetails); if (!empty($options[$selectedProductID])) { $dao->product_option = $options[$selectedProductID][$selectedProductOptionID]; @@ -230,12 +222,12 @@ public static function processPremium($params, $contributionID, $premiumID = NUL $dao->save(); //CRM-11106 if ($premiumID == NULL || $isDeleted) { - $premiumParams = array( + $premiumParams = [ 'cost' => CRM_Utils_Array::value('cost', $productDetails), 'currency' => CRM_Utils_Array::value('currency', $productDetails), 'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $productDetails), 'contributionId' => $contributionID, - ); + ]; if ($isDeleted) { $premiumParams['oldPremium']['product_id'] = $ContributionProduct->product_id; $premiumParams['oldPremium']['contribution_id'] = $ContributionProduct->contribution_id; @@ -254,16 +246,20 @@ public static function processPremium($params, $contributionID, $premiumID = NUL * @param int $contributionNoteID */ public static function processNote($params, $contactID, $contributionID, $contributionNoteID = NULL) { + if (CRM_Utils_System::isNull($params['note']) && $contributionNoteID) { + CRM_Core_BAO_Note::del($contributionNoteID); + return; + } //process note - $noteParams = array( + $noteParams = [ 'entity_table' => 'civicrm_contribution', 'note' => $params['note'], 'entity_id' => $contributionID, 'contact_id' => $contactID, - ); - $noteID = array(); + ]; + $noteID = []; if ($contributionNoteID) { - $noteID = array("id" => $contributionNoteID); + $noteID = ["id" => $contributionNoteID]; $noteParams['note'] = $noteParams['note'] ? $noteParams['note'] : "null"; } CRM_Core_BAO_Note::add($noteParams, $noteID); @@ -277,17 +273,16 @@ public static function processNote($params, $contactID, $contributionID, $contri * @param CRM_Core_Form $form */ public static function postProcessCommon(&$params, &$formatted, &$form) { - $fields = array( + $fields = [ 'non_deductible_amount', 'total_amount', 'fee_amount', - 'net_amount', 'trxn_id', 'invoice_id', 'creditnote_id', 'campaign_id', 'contribution_page_id', - ); + ]; foreach ($fields as $f) { $formatted[$f] = CRM_Utils_Array::value($f, $params); } @@ -344,12 +339,12 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { // retrieve individual prefix value for honoree if (isset($params['soft_credit'])) { - $softCreditTypes = $softCredits = array(); + $softCreditTypes = $softCredits = []; foreach ($params['soft_credit'] as $key => $softCredit) { - $softCredits[$key] = array( + $softCredits[$key] = [ 'Name' => $softCredit['contact_name'], 'Amount' => CRM_Utils_Money::format($softCredit['amount'], $softCredit['currency']), - ); + ]; $softCreditTypes[$key] = $softCredit['soft_credit_type_label']; } $form->assign('softCreditTypes', $softCreditTypes); @@ -374,9 +369,8 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { $params['product_option'] = $form->_options[$productDAO->id][$productOptionID]; } } - if (!empty($params['fulfilled_date'])) { - $form->assign('fulfilled_date', CRM_Utils_Date::processDate($params['fulfilled_date'])); + $form->assign('fulfilled_date', $params['fulfilled_date']); } } @@ -414,16 +408,16 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { //handle custom data if (!empty($params['hidden_custom'])) { - $contribParams = array(array('contribution_id', '=', $params['contribution_id'], 0, 0)); + $contribParams = [['contribution_id', '=', $params['contribution_id'], 0, 0]]; if ($form->_mode == 'test') { - $contribParams[] = array('contribution_test', '=', 1, 0, 0); + $contribParams[] = ['contribution_test', '=', 1, 0, 0]; } //retrieve custom data - $customGroup = array(); + $customGroup = []; foreach ($form->_groupTree as $groupID => $group) { - $customFields = $customValues = array(); + $customFields = $customValues = []; if ($groupID == 'info') { continue; } @@ -468,7 +462,7 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { } list($sendReceipt, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate( - array( + [ 'groupName' => 'msg_tpl_workflow_contribution', 'valueName' => 'contribution_offline_receipt', 'contactId' => $params['contact_id'], @@ -479,7 +473,7 @@ public static function emailReceipt(&$form, &$params, $ccContribution = FALSE) { 'isTest' => $form->_mode == 'test', 'PDFFilename' => ts('receipt') . '.pdf', 'isEmailPdf' => $isEmailPdf, - ) + ] ); return $sendReceipt; diff --git a/CRM/Contribute/Form/AdditionalPayment.php b/CRM/Contribute/Form/AdditionalPayment.php index a3147c1f325a..e359f6b80ec2 100644 --- a/CRM/Contribute/Form/AdditionalPayment.php +++ b/CRM/Contribute/Form/AdditionalPayment.php @@ -1,9 +1,9 @@ contactID - * * @var int + * @deprecated - use parent $this->contactID */ protected $_contactId = NULL; @@ -72,77 +67,67 @@ class CRM_Contribute_Form_AdditionalPayment extends CRM_Contribute_Form_Abstract protected $fromEmailId = NULL; - protected $_fromEmails = NULL; - protected $_view = NULL; public $_action = NULL; + /** + * Pre process form. + * + * @throws \CRM_Core_Exception + */ public function preProcess() { - parent::preProcess(); $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); - // @todo don't set this - rely on parent $this->contactID - $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); - $this->_component = CRM_Utils_Request::retrieve('component', 'String', $this, TRUE); + parent::preProcess(); + $this->_contactId = $this->_contactID; + $this->_component = CRM_Utils_Request::retrieve('component', 'String', $this, FALSE, 'contribution'); $this->_view = CRM_Utils_Request::retrieve('view', 'String', $this, FALSE); $this->assign('component', $this->_component); $this->assign('id', $this->_id); $this->assign('suppressPaymentFormButtons', $this->isBeingCalledFromSelectorContext()); if ($this->_view == 'transaction' && ($this->_action & CRM_Core_Action::BROWSE)) { - $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_id, $this->_component, TRUE); - $transactionRows = $paymentInfo['transaction']; - $title = ts('View Payment'); - if ($this->_component == 'event') { - $info = CRM_Event_BAO_Participant::participantDetails($this->_id); - $title .= " - {$info['title']}"; - } + $title = $this->assignPaymentInfoBlock(); CRM_Utils_System::setTitle($title); - $this->assign('transaction', TRUE); - $this->assign('rows', $transactionRows); return; } - $this->_fromEmails = CRM_Core_BAO_Email::getFromEmail(); - $entityType = 'contribution'; if ($this->_component == 'event') { $entityType = 'participant'; $this->_contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_id, 'contribution_id', 'participant_id'); $eventId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $this->_id, 'event_id', 'id'); - $this->_fromEmails = CRM_Event_BAO_Event::getFromEmailIds($eventId); } else { $this->_contributionId = $this->_id; - $this->_fromEmails['from_email_id'] = CRM_Core_BAO_Email::getFromEmail(); } - $paymentInfo = CRM_Core_BAO_FinancialTrxn::getPartialPaymentWithType($this->_id, $entityType); $paymentDetails = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_id, $this->_component, FALSE, TRUE); + $paymentAmt = CRM_Contribute_BAO_Contribution::getContributionBalance($this->_id); $this->_amtPaid = $paymentDetails['paid']; $this->_amtTotal = $paymentDetails['total']; - if (!empty($paymentInfo['refund_due'])) { - $paymentAmt = $this->_refund = $paymentInfo['refund_due']; + if ($paymentAmt < 0) { + $this->_refund = $paymentAmt; $this->_paymentType = 'refund'; } - elseif (!empty($paymentInfo['amount_owed'])) { - $paymentAmt = $this->_owed = $paymentInfo['amount_owed']; + elseif ($paymentAmt > 0) { + $this->_owed = $paymentAmt; $this->_paymentType = 'owed'; } else { - CRM_Core_Error::fatal(ts('No payment information found for this record')); + throw new CRM_Core_Exception(ts('No payment information found for this record')); } if (!empty($this->_mode) && $this->_paymentType == 'refund') { - CRM_Core_Error::fatal(ts('Credit card payment is not for Refund payments use')); + throw new CRM_Core_Exception(ts('Credit card payment is not for Refund payments use')); } - list($this->_contributorDisplayName, $this->_contributorEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactId); + list($this->_contributorDisplayName, $this->_contributorEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID); $this->assign('contributionMode', $this->_mode); - $this->assign('contactId', $this->_contactId); + $this->assign('contactId', $this->_contactID); $this->assign('paymentType', $this->_paymentType); $this->assign('paymentAmt', abs($paymentAmt)); @@ -153,6 +138,8 @@ public function preProcess() { * Is this function being called from a datatable selector. * * If so we don't want to show the buttons. + * + * @throws \CRM_Core_Exception */ protected function isBeingCalledFromSelectorContext() { return CRM_Utils_Request::retrieve('selector', 'Positive'); @@ -167,6 +154,7 @@ protected function isBeingCalledFromSelectorContext() { * @return array * reference to the array of default values */ + /** * @return array */ @@ -174,7 +162,7 @@ public function setDefaultValues() { if ($this->_view == 'transaction' && ($this->_action & CRM_Core_Action::BROWSE)) { return NULL; } - $defaults = array(); + $defaults = []; if ($this->_mode) { CRM_Core_Payment_Form::setDefaultValues($this, $this->_contactId); $defaults = array_merge($defaults, $this->_defaults); @@ -185,7 +173,10 @@ public function setDefaultValues() { } if ($this->_refund) { - $defaults['total_amount'] = abs($this->_refund); + $defaults['total_amount'] = CRM_Utils_Money::format(abs($this->_refund), NULL, NULL, TRUE); + } + elseif ($this->_owed) { + $defaults['total_amount'] = CRM_Utils_Money::formatLocaleNumericRoundedForDefaultCurrency($this->_owed); } // Set $newCredit variable in template to control whether link to credit card mode is included @@ -198,15 +189,14 @@ public function setDefaultValues() { */ public function buildQuickForm() { if ($this->_view == 'transaction' && ($this->_action & CRM_Core_Action::BROWSE)) { - $this->addButtons(array( - array( - 'type' => 'cancel', - 'name' => ts('Done'), - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'cancel', + 'name' => ts('Done'), + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + ]); return; } @@ -226,12 +216,16 @@ public function buildQuickForm() { //add receipt for offline contribution $this->addElement('checkbox', 'is_email_receipt', ts('Send Receipt?')); - $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails['from_email_id']); + if ($this->_component === 'event') { + $eventID = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $this->_id, 'event_id', 'id'); + } + + $this->add('select', 'from_email_address', ts('Receipt From'), CRM_Financial_BAO_Payment::getValidFromEmailsForPayment($eventID ?? NULL)); $this->add('textarea', 'receipt_text', ts('Confirmation Message')); $dateLabel = ($this->_refund) ? ts('Refund Date') : ts('Date Received'); - $this->addField('trxn_date', array('entity' => 'FinancialTrxn', 'label' => $dateLabel, 'context' => 'Contribution'), FALSE, FALSE); + $this->addField('trxn_date', ['entity' => 'FinancialTrxn', 'label' => $dateLabel, 'context' => 'Contribution'], FALSE, FALSE); if ($this->_contactId && $this->_id) { if ($this->_component == 'event') { @@ -248,17 +242,17 @@ public function buildQuickForm() { $js = NULL; // render backoffice payment fields only on offline mode if (!$this->_mode) { - $js = array('onclick' => "return verify( );"); + $js = ['onclick' => "return verify( );"]; $this->add('select', 'payment_instrument_id', ts('Payment Method'), - array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::paymentInstrument(), - FALSE, - array('onChange' => "return showHideByValue('payment_instrument_id','4','checkNumber','table-row','select',false);") + ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::paymentInstrument(), + TRUE, + ['onChange' => "return showHideByValue('payment_instrument_id','4','checkNumber','table-row','select',false);"] ); $this->add('text', 'check_number', ts('Check Number'), $attributes['financial_trxn_check_number']); - $this->add('text', 'trxn_id', ts('Transaction ID'), array('class' => 'twelve') + $attributes['trxn_id']); + $this->add('text', 'trxn_id', ts('Transaction ID'), ['class' => 'twelve'] + $attributes['trxn_id']); $this->add('text', 'fee_amount', ts('Fee Amount'), $attributes['fee_amount'] @@ -271,24 +265,23 @@ public function buildQuickForm() { $this->addRule('net_amount', ts('Please enter a valid monetary value for Net Amount.'), 'money'); } - $buttonName = $this->_refund ? 'Record Refund' : 'Record Payment'; - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('%1', array(1 => $buttonName)), - 'js' => $js, - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $buttonName = $this->_refund ? ts('Record Refund') : ts('Record Payment'); + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => $buttonName, + 'js' => $js, + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); $mailingInfo = Civi::settings()->get('mailing_backend'); $this->assign('outBound_option', $mailingInfo['outBound_option']); - $this->addFormRule(array('CRM_Contribute_Form_AdditionalPayment', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_AdditionalPayment', 'formRule'], $this); } /** @@ -299,14 +292,14 @@ public function buildQuickForm() { * @return array */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; if ($self->_paymentType == 'owed' && $fields['total_amount'] > $self->_owed) { $errors['total_amount'] = ts('Payment amount cannot be greater than owed amount'); } if ($self->_paymentType == 'refund' && $fields['total_amount'] != abs($self->_refund)) { $errors['total_amount'] = ts('Refund amount must equal refund due amount.'); } - $netAmt = $fields['total_amount'] - CRM_Utils_Array::value('fee_amount', $fields, 0); + $netAmt = (float) $fields['total_amount'] - (float) CRM_Utils_Array::value('fee_amount', $fields, 0); if (!empty($fields['net_amount']) && $netAmt != $fields['net_amount']) { $errors['net_amount'] = ts('Net amount should be equal to the difference between payment amount and fee amount.'); } @@ -335,8 +328,10 @@ public function postProcess() { /** * Process Payments. + * * @param array $submittedValues * + * @throws \CiviCRM_API3_Exception */ public function submit($submittedValues) { $this->_params = $submittedValues; @@ -347,20 +342,6 @@ public function submit($submittedValues) { if ($this->_component == 'event') { $participantId = $this->_id; } - $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', - 'contribution_status_id', - array('labelColumn' => 'name') - ); - $contributionStatusID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->_contributionId, 'contribution_status_id'); - if ($contributionStatuses[$contributionStatusID] == 'Pending') { - civicrm_api3('Contribution', 'create', - array( - 'id' => $this->_contributionId, - 'contribution_status_id' => array_search('Partially paid', $contributionStatuses), - 'is_pay_later' => 0, - ) - ); - } if ($this->_mode) { // process credit card @@ -368,27 +349,34 @@ public function submit($submittedValues) { $this->processCreditCard(); } - $defaults = array(); - $contribution = civicrm_api3('Contribution', 'getsingle', array( - 'return' => array("contribution_status_id"), - 'id' => $this->_contributionId, - )); - $contributionStatusId = CRM_Utils_Array::value('contribution_status_id', $contribution); - $result = CRM_Contribute_BAO_Contribution::recordAdditionalPayment($this->_contributionId, $this->_params, $this->_paymentType, $participantId); - // Fetch the contribution & do proportional line item assignment - $params = array('id' => $this->_contributionId); - $contribution = CRM_Contribute_BAO_Contribution::retrieve($params, $defaults, $params); - CRM_Contribute_BAO_Contribution::addPayments(array($contribution), $contributionStatusId); + $trxnsData = $this->_params; + if ($this->_paymentType == 'refund') { + $trxnsData['total_amount'] = -$trxnsData['total_amount']; + } + $trxnsData['participant_id'] = $participantId; + $trxnsData['contribution_id'] = $this->_contributionId; + // From the + $trxnsData['is_send_contribution_notification'] = FALSE; + $paymentID = civicrm_api3('Payment', 'create', $trxnsData)['id']; + + if ($this->_contributionId && CRM_Core_Permission::access('CiviMember')) { + $membershipPaymentCount = civicrm_api3('MembershipPayment', 'getCount', ['contribution_id' => $this->_contributionId]); + if ($membershipPaymentCount) { + $this->ajaxResponse['updateTabs']['#tab_member'] = CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactID); + } + } + if ($this->_contributionId && CRM_Core_Permission::access('CiviEvent')) { + $participantPaymentCount = civicrm_api3('ParticipantPayment', 'getCount', ['contribution_id' => $this->_contributionId]); + if ($participantPaymentCount) { + $this->ajaxResponse['updateTabs']['#tab_participant'] = CRM_Contact_BAO_Contact::getCountComponent('participant', $this->_contactID); + } + } $statusMsg = ts('The payment record has been processed.'); // send email - if (!empty($result) && !empty($this->_params['is_email_receipt'])) { - $this->_params['contact_id'] = $this->_contactId; - $this->_params['contribution_id'] = $this->_contributionId; - // to get 'from email id' for send receipt - $this->fromEmailId = $this->_params['from_email_address']; - $sendReceipt = $this->emailReceipt($this->_params); - if ($sendReceipt) { + if (!empty($paymentID) && !empty($this->_params['is_email_receipt'])) { + $sendResult = civicrm_api3('Payment', 'sendconfirmation', ['id' => $paymentID, 'from' => $submittedValues['from_email_address']])['values'][$paymentID]; + if ($sendResult['is_sent']) { $statusMsg .= ' ' . ts('A receipt has been emailed to the contributor.'); } } @@ -401,7 +389,7 @@ public function processCreditCard() { $session = CRM_Core_Session::singleton(); $now = date('YmdHis'); - $fields = array(); + $fields = []; // we need to retrieve email address if ($this->_context == 'standalone' && !empty($this->_params['is_email_receipt'])) { @@ -474,7 +462,7 @@ public function processCreditCard() { catch (\Civi\Payment\Exception\PaymentProcessorException $e) { Civi::log()->error('Payment processor exception: ' . $e->getMessage()); $urlParams = "action=add&cid={$this->_contactId}&id={$this->_contributionId}&component={$this->_component}&mode={$this->_mode}"; - CRM_Core_Error::statusBounce(CRM_Utils_System::url($e->getMessage(), 'civicrm/payment/add', $urlParams)); + CRM_Core_Error::statusBounce($e->getMessage(), CRM_Utils_System::url('civicrm/payment/add', $urlParams)); } } @@ -494,92 +482,8 @@ public function processCreditCard() { $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, 'sort_name' ); - $this->_params['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); - } - } - - /** - * Function to send email receipt. - * - * @param array $params - * - * @return bool - */ - public function emailReceipt(&$params) { - // email receipt sending - // send message template - if ($this->_component == 'event') { - - // fetch event information from participant ID using API - $eventId = civicrm_api3('Participant', 'getvalue', array( - 'return' => "event_id", - 'id' => $this->_id, - )); - $event = civicrm_api3('Event', 'getsingle', array('id' => $eventId)); - - $this->assign('event', $event); - $this->assign('isShowLocation', $event['is_show_location']); - if (CRM_Utils_Array::value('is_show_location', $event) == 1) { - $locationParams = array( - 'entity_id' => $eventId, - 'entity_table' => 'civicrm_event', - ); - $location = CRM_Core_BAO_Location::getValues($locationParams, TRUE); - $this->assign('location', $location); - } - } - - // assign payment info here - $paymentConfig['confirm_email_text'] = CRM_Utils_Array::value('confirm_email_text', $params); - $this->assign('paymentConfig', $paymentConfig); - - $this->assign('totalAmount', $this->_amtTotal); - - $isRefund = ($this->_paymentType == 'refund') ? TRUE : FALSE; - $this->assign('isRefund', $isRefund); - if ($isRefund) { - $this->assign('totalPaid', $this->_amtPaid); - $this->assign('refundAmount', $params['total_amount']); - } - else { - $balance = $this->_amtTotal - ($this->_amtPaid + $params['total_amount']); - $paymentsComplete = ($balance == 0) ? 1 : 0; - $this->assign('amountOwed', $balance); - $this->assign('paymentAmount', $params['total_amount']); - $this->assign('paymentsComplete', $paymentsComplete); - } - $this->assign('contactDisplayName', $this->_contributorDisplayName); - - // assign trxn details - $this->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $params)); - $this->assign('receive_date', CRM_Utils_Array::value('trxn_date', $params)); - $this->assign('paidBy', CRM_Core_PseudoConstant::getLabel( - 'CRM_Contribute_BAO_Contribute', - 'payment_instrument_id', - $params['payment_instrument_id'] - )); - $this->assign('checkNumber', CRM_Utils_Array::value('check_number', $params)); - - $sendTemplateParams = array( - 'groupName' => 'msg_tpl_workflow_contribution', - 'valueName' => 'payment_or_refund_notification', - 'contactId' => $this->_contactId, - 'PDFFilename' => ts('notification') . '.pdf', - ); - - // try to send emails only if email id is present - // and the do-not-email option is not checked for that contact - if ($this->_contributorEmail && !$this->_toDoNotEmail) { - if (array_key_exists($params['from_email_address'], $this->_fromEmails['from_email_id'])) { - $receiptFrom = $params['from_email_address']; - } - - $sendTemplateParams['from'] = $receiptFrom; - $sendTemplateParams['toName'] = $this->_contributorDisplayName; - $sendTemplateParams['toEmail'] = $this->_contributorEmail; + $this->_params['source'] = ts('Submit Credit Card Payment by: %1', [1 => $userSortName]); } - list($mailSent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); - return $mailSent; } /** @@ -602,18 +506,18 @@ public function testSubmit($params, $creditCardMode = NULL, $entityType = 'contr if (!empty($params['contribution_id'])) { $this->_contributionId = $params['contribution_id']; - $paymentInfo = CRM_Core_BAO_FinancialTrxn::getPartialPaymentWithType($this->_contributionId, $entityType); $paymentDetails = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_contributionId, $entityType, FALSE, TRUE); + $paymentAmount = CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId); $this->_amtPaid = $paymentDetails['paid']; $this->_amtTotal = $paymentDetails['total']; - if (!empty($paymentInfo['refund_due'])) { - $this->_refund = $paymentInfo['refund_due']; + if ($paymentAmount < 0) { + $this->_refund = $paymentAmount; $this->_paymentType = 'refund'; } - elseif (!empty($paymentInfo['amount_owed'])) { - $this->_owed = $paymentInfo['amount_owed']; + elseif ($paymentAmount > 0) { + $this->_owed = $paymentAmount; $this->_paymentType = 'owed'; } } @@ -626,7 +530,7 @@ public function testSubmit($params, $creditCardMode = NULL, $entityType = 'contr $this->_mode = $creditCardMode; } - $this->_fields = array(); + $this->_fields = []; $this->set('cid', $this->_contactId); parent::preProcess(); $this->submit($params); diff --git a/CRM/Contribute/Form/CancelSubscription.php b/CRM/Contribute/Form/CancelSubscription.php index adc18069e8df..ef9dbe32ca32 100644 --- a/CRM/Contribute/Form/CancelSubscription.php +++ b/CRM/Contribute/Form/CancelSubscription.php @@ -1,9 +1,9 @@ _mid = CRM_Utils_Request::retrieve('mid', 'Integer', $this, FALSE); - - $this->_crid = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); + parent::preProcess(); if ($this->_crid) { - $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_crid, 'recur', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_crid); $this->assign('frequency_unit', $this->_subscriptionDetails->frequency_unit); $this->assign('frequency_interval', $this->_subscriptionDetails->frequency_interval); $this->assign('amount', $this->_subscriptionDetails->amount); @@ -74,23 +76,17 @@ public function preProcess() { $this->_mode = 'auto_renew'; // CRM-18468: crid is more accurate than mid for getting // subscriptionDetails, so don't get them again. - if (!$this->_crid) { - $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_mid, 'membership', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_mid, 'membership'); - } $membershipTypes = CRM_Member_PseudoConstant::membershipType(); $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->_mid, 'membership_type_id'); $this->assign('membershipType', CRM_Utils_Array::value($membershipTypeId, $membershipTypes)); } - $this->_coid = CRM_Utils_Request::retrieve('coid', 'Integer', $this, FALSE); if ($this->_coid) { if (CRM_Contribute_BAO_Contribution::isSubscriptionCancelled($this->_coid)) { CRM_Core_Error::fatal(ts('The recurring contribution looks to have been cancelled already.')); } $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_coid, 'contribution'); $this->assign('frequency_unit', $this->_subscriptionDetails->frequency_unit); $this->assign('frequency_interval', $this->_subscriptionDetails->frequency_interval); @@ -102,38 +98,53 @@ public function preProcess() { (!$this->_crid && !$this->_coid && !$this->_mid) || (!$this->_subscriptionDetails) ) { - CRM_Core_Error::fatal('Required information missing.'); + CRM_Core_Error::statusBounce('Required information missing.'); } - if (!CRM_Core_Permission::check('edit contributions')) { - $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE); - if (!CRM_Contact_BAO_Contact_Utils::validChecksum($this->_subscriptionDetails->contact_id, $userChecksum)) { - CRM_Core_Error::fatal(ts('You do not have permission to cancel this recurring contribution.')); - } - $this->_selfService = TRUE; - } - $this->assign('self_service', $this->_selfService); - // handle context redirection CRM_Contribute_BAO_ContributionRecur::setSubscriptionContext(); CRM_Utils_System::setTitle($this->_mid ? ts('Cancel Auto-renewal') : ts('Cancel Recurring Contribution')); $this->assign('mode', $this->_mode); + if ($this->isSelfService()) { + unset($this->entityFields['send_cancel_request'], $this->entityFields['is_notify']); + } + if ($this->_subscriptionDetails->contact_id) { list($this->_donorDisplayName, $this->_donorEmail) = CRM_Contact_BAO_Contact::getContactDetails($this->_subscriptionDetails->contact_id); } } + /** + * Set entity fields for this cancellation. + */ + public function setEntityFields() { + $this->entityFields = [ + 'cancel_reason' => ['name' => 'cancel_reason'], + ]; + $this->entityFields['send_cancel_request'] = [ + 'title' => ts('Send cancellation request?'), + 'name' => 'send_cancel_request', + 'not-auto-addable' => TRUE, + ]; + $this->entityFields['is_notify'] = [ + 'title' => ts('Notify Contributor?'), + 'name' => 'is_notify', + 'not-auto-addable' => TRUE, + ]; + } + /** * Build the form object. */ public function buildQuickForm() { + $this->buildQuickEntityForm(); // Determine if we can cancel recurring contribution via API with this processor $cancelSupported = $this->_paymentProcessorObj->supports('CancelRecurring'); if ($cancelSupported) { - $searchRange = array(); + $searchRange = []; $searchRange[] = $this->createElement('radio', NULL, NULL, ts('Yes'), '1'); $searchRange[] = $this->createElement('radio', NULL, NULL, ts('No'), '0'); @@ -141,7 +152,7 @@ public function buildQuickForm() { $searchRange, 'send_cancel_request', ts('Send cancellation request to %1 ?', - array(1 => $this->_paymentProcessorObj->_processorName)) + [1 => $this->_paymentProcessorObj->_processorName]) ); } $this->assign('cancelSupported', $cancelSupported); @@ -157,23 +168,22 @@ public function buildQuickForm() { } $type = 'next'; - if ($this->_selfService) { + if ($this->isSelfService()) { $type = 'submit'; } - $this->addButtons(array( - array( - 'type' => $type, - 'name' => $cancelButton, - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Not Now'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => $type, + 'name' => $cancelButton, + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Not Now'), + ], + ]); } /** @@ -183,10 +193,10 @@ public function buildQuickForm() { * array of default values */ public function setDefaultValues() { - return array( + return [ 'is_notify' => 1, 'send_cancel_request' => 1, - ); + ]; } /** @@ -197,7 +207,7 @@ public function postProcess() { $cancelSubscription = TRUE; $params = $this->controller->exportValues($this->_name); - if ($this->_selfService) { + if ($this->isSelfService()) { // for self service force sending-request & notify if ($this->_paymentProcessorObj->supports('cancelRecurring')) { $params['send_cancel_request'] = 1; @@ -209,7 +219,7 @@ public function postProcess() { } if (CRM_Utils_Array::value('send_cancel_request', $params) == 1) { - $cancelParams = array('subscriptionId' => $this->_subscriptionDetails->subscription_id); + $cancelParams = ['subscriptionId' => $this->_subscriptionDetails->subscription_id]; $cancelSubscription = $this->_paymentProcessorObj->cancelSubscription($message, $cancelParams); } @@ -217,28 +227,24 @@ public function postProcess() { CRM_Core_Error::displaySessionError($cancelSubscription); } elseif ($cancelSubscription) { - $activityParams - = array( - 'subject' => $this->_mid ? ts('Auto-renewal membership cancelled') : ts('Recurring contribution cancelled'), - 'details' => $message, - ); - $cancelStatus = CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution( - $this->_subscriptionDetails->recur_id, - NULL, - $activityParams - ); - - if ($cancelStatus) { - $tplParams = array(); + try { + civicrm_api3('ContributionRecur', 'cancel', [ + 'id' => $this->_subscriptionDetails->recur_id, + 'membership_id' => $this->_mid, + 'processor_message' => $message, + 'cancel_reason' => $params['cancel_reason'], + ]); + + $tplParams = []; if ($this->_mid) { - $inputParams = array('id' => $this->_mid); + $inputParams = ['id' => $this->_mid]; CRM_Member_BAO_Membership::getValues($inputParams, $tplParams); $tplParams = $tplParams[$this->_mid]; $tplParams['membership_status'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus', $tplParams['status_id']); $tplParams['membershipType'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $tplParams['membership_type_id']); - $status = ts('The automatic renewal of your %1 membership has been cancelled as requested. This does not affect the status of your membership - you will receive a separate notification when your membership is up for renewal.', array(1 => $tplParams['membershipType'])); + $status = ts('The automatic renewal of your %1 membership has been cancelled as requested. This does not affect the status of your membership - you will receive a separate notification when your membership is up for renewal.', [1 => $tplParams['membershipType']]); $msgTitle = 'Membership Renewal Cancelled'; $msgType = 'info'; } @@ -246,13 +252,13 @@ public function postProcess() { $tplParams['recur_frequency_interval'] = $this->_subscriptionDetails->frequency_interval; $tplParams['recur_frequency_unit'] = $this->_subscriptionDetails->frequency_unit; $tplParams['amount'] = $this->_subscriptionDetails->amount; - $tplParams['contact'] = array('display_name' => $this->_donorDisplayName); + $tplParams['contact'] = ['display_name' => $this->_donorDisplayName]; $status = ts('The recurring contribution of %1, every %2 %3 has been cancelled.', - array( + [ 1 => $this->_subscriptionDetails->amount, 2 => $this->_subscriptionDetails->frequency_interval, 3 => $this->_subscriptionDetails->frequency_unit, - ) + ] ); $msgTitle = 'Contribution Cancelled'; $msgType = 'success'; @@ -265,7 +271,7 @@ public function postProcess() { 'id', $this->_subscriptionDetails->contribution_page_id, $value, - array('title', 'receipt_from_name', 'receipt_from_email') + ['title', 'receipt_from_name', 'receipt_from_email'] ); $receiptFrom = '"' . CRM_Utils_Array::value('receipt_from_name', $value[$this->_subscriptionDetails->contribution_page_id]) . @@ -280,7 +286,7 @@ public function postProcess() { // send notification $sendTemplateParams - = array( + = [ 'groupName' => $this->_mode == 'auto_renew' ? 'msg_tpl_workflow_membership' : 'msg_tpl_workflow_contribution', 'valueName' => $this->_mode == 'auto_renew' ? 'membership_autorenew_cancelled' : 'contribution_recurring_cancelled', 'contactId' => $this->_subscriptionDetails->contact_id, @@ -290,11 +296,11 @@ public function postProcess() { 'from' => $receiptFrom, 'toName' => $this->_donorDisplayName, 'toEmail' => $this->_donorEmail, - ); + ]; list($sent) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); } } - else { + catch (CiviCRM_API3_Exception $e) { $msgType = 'error'; $msgTitle = ts('Error'); if ($params['send_cancel_request'] == 1) { diff --git a/CRM/Contribute/Form/Contribution.php b/CRM/Contribute/Form/Contribution.php index b39bcaa728de..057ba8f14247 100644 --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@ -1,9 +1,9 @@ assign('action', $this->_action); // Get the contribution id if update - $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); + $this->_id = CRM_Utils_Request::retrieve('id', 'Positive'); if (!empty($this->_id)) { + $this->assignPaymentInfoBlock(); $this->assign('contribID', $this->_id); + $this->assign('isUsePaymentBlock', TRUE); } - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $this->assign('context', $this->_context); $this->_compId = CRM_Utils_Request::retrieve('compId', 'Positive', $this); @@ -243,15 +265,13 @@ public function preProcess() { $this->_compContext = CRM_Utils_Request::retrieve('compContext', 'String', $this); //set the contribution mode. - $this->_mode = CRM_Utils_Request::retrieve('mode', 'String', $this); + $this->_mode = CRM_Utils_Request::retrieve('mode', 'Alphanumeric', $this); $this->assign('contributionMode', $this->_mode); if ($this->_action & CRM_Core_Action::DELETE) { return; } - $this->assign('showCheckNumber', TRUE); - $this->_fromEmails = CRM_Core_BAO_Email::getFromEmail(); if (in_array('CiviPledge', CRM_Core_Config::singleton()->enableComponents) && !$this->_formType) { @@ -261,7 +281,7 @@ public function preProcess() { if ($this->_id) { $this->showRecordLinkMesssage($this->_id); } - $this->_values = array(); + $this->_values = []; // Current contribution id. if ($this->_id) { @@ -274,7 +294,7 @@ public function preProcess() { $this->applyCustomData('Contribution', CRM_Utils_Array::value('financial_type_id', $_POST), $this->_id); } - $this->_lineItems = array(); + $this->_lineItems = []; if ($this->_id) { if (!empty($this->_compId) && $this->_compContext == 'participant') { $this->assign('compId', $this->_compId); @@ -287,7 +307,7 @@ public function preProcess() { empty($lineItem) ? NULL : $this->_lineItems[] = $lineItem; } - $this->assign('lineItem', empty($lineItem) ? FALSE : array($lineItem)); + $this->assign('lineItem', empty($lineItem) ? FALSE : [$lineItem]); // Set title if ($this->_mode && $this->_id) { @@ -301,10 +321,6 @@ public function preProcess() { else { $this->setPageTitle($this->_ppID ? ts('Pledge Payment') : ts('Contribution')); } - - if ($this->_id) { - CRM_Contribute_Form_SoftCredit::preprocess($this); - } } /** @@ -313,7 +329,6 @@ public function preProcess() { * @return array */ public function setDefaultValues() { - $defaults = $this->_values; // Set defaults for pledge payment. @@ -359,23 +374,21 @@ public function setDefaultValues() { // Fix the display of the monetary value, CRM-4038. if (isset($defaults['total_amount'])) { + $total_value = $defaults['total_amount']; + $defaults['total_amount'] = CRM_Utils_Money::format($total_value, NULL, '%a'); if (!empty($defaults['tax_amount'])) { $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); if (!(CRM_Utils_Array::value('membership', $componentDetails) || CRM_Utils_Array::value('participant', $componentDetails))) { - $defaults['total_amount'] = CRM_Utils_Money::format($defaults['total_amount'] - $defaults['tax_amount'], NULL, '%a'); + $defaults['total_amount'] = CRM_Utils_Money::format($total_value - $defaults['tax_amount'], NULL, '%a'); } } - else { - $defaults['total_amount'] = CRM_Utils_Money::format($defaults['total_amount'], NULL, '%a'); - } - } - - if (isset($defaults['non_deductible_amount'])) { - $defaults['non_deductible_amount'] = CRM_Utils_Money::format($defaults['non_deductible_amount'], NULL, '%a'); } - if (isset($defaults['fee_amount'])) { - $defaults['fee_amount'] = CRM_Utils_Money::format($defaults['fee_amount'], NULL, '%a'); + $amountFields = ['non_deductible_amount', 'fee_amount']; + foreach ($amountFields as $amt) { + if (isset($defaults[$amt])) { + $defaults[$amt] = CRM_Utils_Money::format($defaults[$amt], NULL, '%a'); + } } if ($this->_contributionType) { @@ -400,13 +413,13 @@ public function setDefaultValues() { } $options_key = CRM_Utils_Array::key($this->_productDAO->product_option, $options); if ($options_key) { - $defaults['product_name'] = array($this->_productDAO->product_id, trim($options_key)); + $defaults['product_name'] = [$this->_productDAO->product_id, trim($options_key)]; } else { - $defaults['product_name'] = array($this->_productDAO->product_id); + $defaults['product_name'] = [$this->_productDAO->product_id]; } if ($this->_productDAO->fulfilled_date) { - list($defaults['fulfilled_date']) = CRM_Utils_Date::setDateDefaults($this->_productDAO->fulfilled_date); + $defaults['fulfilled_date'] = $this->_productDAO->fulfilled_date; } } @@ -421,35 +434,18 @@ public function setDefaultValues() { if (!empty($defaults['contribution_status_id']) && in_array( CRM_Contribute_PseudoConstant::contributionStatus($defaults['contribution_status_id'], 'name'), // Historically not 'Cancelled' hence not using CRM_Contribute_BAO_Contribution::isContributionStatusNegative. - array('Refunded', 'Chargeback') + ['Refunded', 'Chargeback'] )) { $defaults['refund_trxn_id'] = CRM_Core_BAO_FinancialTrxn::getRefundTransactionTrxnID($this->_id); } else { $defaults['refund_trxn_id'] = isset($defaults['trxn_id']) ? $defaults['trxn_id'] : NULL; } - $dates = array( - 'receive_date', - 'receipt_date', - 'cancel_date', - 'thankyou_date', - ); - foreach ($dates as $key) { - if (!empty($defaults[$key])) { - list($defaults[$key], $defaults[$key . '_time']) - = CRM_Utils_Date::setDateDefaults(CRM_Utils_Array::value($key, $defaults), 'activityDateTime'); - } - } if (!$this->_id && empty($defaults['receive_date'])) { - list($defaults['receive_date'], - $defaults['receive_date_time'] - ) = CRM_Utils_Date::setDateDefaults(NULL, 'activityDateTime'); + $defaults['receive_date'] = date('Y-m-d H:i:s'); } - $this->assign('receive_date', CRM_Utils_Date::processDate(CRM_Utils_Array::value('receive_date', $defaults), - CRM_Utils_Array::value('receive_date_time', $defaults) - )); $currency = CRM_Utils_Array::value('currency', $defaults); $this->assign('currency', $currency); // Hack to get currency info to the js layer. CRM-11440. @@ -470,6 +466,26 @@ public function setDefaultValues() { * Build the form object. */ public function buildQuickForm() { + if ($this->_id) { + $this->add('hidden', 'id', $this->_id); + } + + if ($this->_action & CRM_Core_Action::DELETE) { + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Delete'), + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); + return; + } + // FIXME: This probably needs to be done in preprocess if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() && $this->_action & CRM_Core_Action::UPDATE @@ -481,18 +497,19 @@ public function buildQuickForm() { CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); } } - $allPanes = array(); - $recurJs = NULL; + $allPanes = []; + //tax rate from financialType $this->assign('taxRates', json_encode(CRM_Core_PseudoConstant::getTaxRates())); $this->assign('currencies', json_encode(CRM_Core_OptionGroup::values('currencies_enabled'))); // build price set form. $buildPriceSet = FALSE; - $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); + $invoicing = CRM_Invoicing_Utils::isInvoicingEnabled(); $this->assign('invoicing', $invoicing); + $buildRecurBlock = FALSE; + // display tax amount on edit contribution page if ($invoicing && $this->_action & CRM_Core_Action::UPDATE && isset($this->_values['tax_amount'])) { $this->assign('totalTaxAmount', $this->_values['tax_amount']); @@ -520,14 +537,13 @@ public function buildQuickForm() { $this->assign('buildPriceSet', $buildPriceSet); $defaults = $this->_values; - $additionalDetailFields = array( + $additionalDetailFields = [ 'note', 'thankyou_date', 'invoice_id', 'non_deductible_amount', 'fee_amount', - 'net_amount', - ); + ]; foreach ($additionalDetailFields as $key) { if (!empty($defaults[$key])) { $defaults['hidden_AdditionalDetail'] = 1; @@ -542,12 +558,12 @@ public function buildQuickForm() { } if ($this->_noteID && - isset($this->_values['note']) + !CRM_Utils_System::isNull($this->_values['note']) ) { $defaults['hidden_AdditionalDetail'] = 1; } - $paneNames = array(); + $paneNames = []; if (empty($this->_payNow)) { $paneNames[ts('Additional Details')] = 'AdditionalDetail'; } @@ -560,7 +576,8 @@ public function buildQuickForm() { $paneNames[ts('Premium Information')] = 'Premium'; } - if (CRM_Core_Payment_Form::buildPaymentForm($this, $this->_paymentProcessor, FALSE, TRUE, $this->getDefaultPaymentInstrumentId()) == TRUE) { + $this->payment_instrument_id = CRM_Utils_Array::value('payment_instrument_id', $defaults, $this->getDefaultPaymentInstrumentId()); + if (CRM_Core_Payment_Form::buildPaymentForm($this, $this->_paymentProcessor, FALSE, TRUE, $this->payment_instrument_id) == TRUE) { if (!empty($this->_recurPaymentProcessors)) { $buildRecurBlock = TRUE; if ($this->_ppID) { @@ -575,12 +592,12 @@ public function buildQuickForm() { } if ($buildRecurBlock) { CRM_Contribute_Form_Contribution_Main::buildRecur($this); - $this->setDefaults(array('is_recur' => 0)); + $this->setDefaults(['is_recur' => 0]); $this->assign('buildRecurBlock', TRUE); - $recurJs = array('onChange' => "buildRecurBlock( this.value ); return false;"); } } } + $this->addPaymentProcessorSelect(FALSE, $buildRecurBlock); foreach ($paneNames as $name => $type) { $allPanes[$name] = $this->generatePane($type, $defaults); @@ -590,7 +607,7 @@ public function buildQuickForm() { $this->assign('qfKey', $qfKey); $this->assign('allPanes', $allPanes); - $this->addFormRule(array('CRM_Contribute_Form_Contribution', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_Contribution', 'formRule'], $this); if ($this->_formType) { $this->assign('formType', $this->_formType); @@ -599,33 +616,16 @@ public function buildQuickForm() { $this->applyFilter('__ALL__', 'trim'); - if ($this->_action & CRM_Core_Action::DELETE) { - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Delete'), - 'spacing' => '         ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); - return; - } - //need to assign custom data type and subtype to the template $this->assign('customDataType', 'Contribution'); $this->assign('customDataSubType', $this->_contributionType); $this->assign('entityID', $this->_id); if ($this->_context == 'standalone') { - $this->addEntityRef('contact_id', ts('Contact'), array( - 'create' => TRUE, - 'api' => array('extra' => array('email')), - ), TRUE); + $this->addEntityRef('contact_id', ts('Contact'), [ + 'create' => TRUE, + 'api' => ['extra' => ['email']], + ], TRUE); } $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Contribution'); @@ -637,114 +637,55 @@ public function buildQuickForm() { } $financialType = $this->add('select', 'financial_type_id', ts('Financial Type'), - array('' => ts('- select -')) + $financialTypes, + ['' => ts('- select -')] + $financialTypes, TRUE, - array('onChange' => "CRM.buildCustomData( 'Contribution', this.value );") + ['onChange' => "CRM.buildCustomData( 'Contribution', this.value );"] ); $paymentInstrument = FALSE; if (!$this->_mode) { + // payment_instrument isn't required in edit and will not be present when payment block is enabled. + $required = $this->_id ? FALSE : TRUE; $checkPaymentID = array_search('Check', CRM_Contribute_PseudoConstant::paymentInstrument('name')); $paymentInstrument = $this->add('select', 'payment_instrument_id', ts('Payment Method'), - array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::paymentInstrument(), - TRUE, array('onChange' => "return showHideByValue('payment_instrument_id','{$checkPaymentID}','checkNumber','table-row','select',false);") + ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::paymentInstrument(), + $required, ['onChange' => "return showHideByValue('payment_instrument_id','{$checkPaymentID}','checkNumber','table-row','select',false);"] ); } - $trxnId = $this->add('text', 'trxn_id', ts('Transaction ID'), array('class' => 'twelve') + $attributes['trxn_id']); + $trxnId = $this->add('text', 'trxn_id', ts('Transaction ID'), ['class' => 'twelve'] + $attributes['trxn_id']); //add receipt for offline contribution $this->addElement('checkbox', 'is_email_receipt', ts('Send Receipt?')); $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails); - $status = CRM_Contribute_PseudoConstant::contributionStatus(); - - // suppressing contribution statuses that are NOT relevant to pledges (CRM-5169) - $statusName = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - if ($this->_ppID) { - foreach (array( - 'Cancelled', - 'Failed', - 'In Progress', - ) as $suppress) { - unset($status[CRM_Utils_Array::key($suppress, $statusName)]); - } - } - elseif ((!$this->_ppID && $this->_id) || !$this->_id) { - $suppressFlag = FALSE; - if ($this->_id) { - $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); - if (CRM_Utils_Array::value('membership', $componentDetails) || CRM_Utils_Array::value('participant', $componentDetails)) { - $suppressFlag = TRUE; - } - } - if (!$suppressFlag) { - foreach (array( - 'Overdue', - 'In Progress', - ) as $suppress) { - unset($status[CRM_Utils_Array::key($suppress, $statusName)]); - } + $component = 'contribution'; + $componentDetails = []; + if ($this->_id) { + $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); + if (!empty($componentDetails['membership'])) { + $component = 'membership'; } - else { - unset($status[CRM_Utils_Array::key('Overdue', $statusName)]); + elseif (!empty($componentDetails['participant'])) { + $component = 'participant'; } } + if ($this->_ppID) { + $component = 'pledge'; + } + $status = CRM_Contribute_BAO_Contribution_Utils::getContributionStatuses($component, $this->_id); // define the status IDs that show the cancellation info, see CRM-17589 - $cancelInfo_show_ids = array(); - foreach (array_keys($statusName) as $status_id) { + $cancelInfo_show_ids = []; + foreach (array_keys($status) as $status_id) { if (CRM_Contribute_BAO_Contribution::isContributionStatusNegative($status_id)) { $cancelInfo_show_ids[] = "'$status_id'"; } } $this->assign('cancelInfo_show_ids', implode(',', $cancelInfo_show_ids)); - if ($this->_id) { - $contributionStatus = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->_id, 'contribution_status_id'); - $name = CRM_Utils_Array::value($contributionStatus, $statusName); - switch ($name) { - case 'Completed': - // [CRM-17498] Removing unsupported status change options. - unset($status[CRM_Utils_Array::key('Pending', $statusName)]); - unset($status[CRM_Utils_Array::key('Failed', $statusName)]); - unset($status[CRM_Utils_Array::key('Partially paid', $statusName)]); - unset($status[CRM_Utils_Array::key('Pending refund', $statusName)]); - case 'Cancelled': - case 'Chargeback': - case 'Refunded': - unset($status[CRM_Utils_Array::key('In Progress', $statusName)]); - unset($status[CRM_Utils_Array::key('Pending', $statusName)]); - unset($status[CRM_Utils_Array::key('Failed', $statusName)]); - break; - - case 'Pending': - case 'In Progress': - unset($status[CRM_Utils_Array::key('Refunded', $statusName)]); - unset($status[CRM_Utils_Array::key('Chargeback', $statusName)]); - break; - - case 'Failed': - foreach (array( - 'Pending', - 'Refunded', - 'Chargeback', - 'Completed', - 'In Progress', - 'Cancelled', - ) as $suppress) { - unset($status[CRM_Utils_Array::key($suppress, $statusName)]); - } - break; - } - } - else { - unset($status[CRM_Utils_Array::key('Refunded', $statusName)]); - unset($status[CRM_Utils_Array::key('Chargeback', $statusName)]); - } - $statusElement = $this->add('select', 'contribution_status_id', ts('Contribution Status'), $status, @@ -759,7 +700,7 @@ public function buildQuickForm() { } // CRM-16189, add Revenue Recognition Date - if (CRM_Contribute_BAO_Contribution::checkContributeSettings('deferred_revenue_enabled')) { + if (Civi::settings()->get('deferred_revenue_enabled')) { $revenueDate = $this->add('date', 'revenue_recognition_date', ts('Revenue Recognition Date'), CRM_Core_SelectValues::date(NULL, 'M Y', NULL, 5)); if ($this->_id && !CRM_Contribute_BAO_Contribution::allowUpdateRevenueRecognitionDate($this->_id)) { $revenueDate->freeze(); @@ -767,29 +708,15 @@ public function buildQuickForm() { } // add various dates - $this->addDateTime('receive_date', ts('Received'), FALSE, array('formatType' => 'activityDateTime')); + $this->addField('receive_date', ['entity' => 'contribution'], !$this->_mode, FALSE); + $this->addField('receipt_date', ['entity' => 'contribution'], FALSE, FALSE); + $this->addField('cancel_date', ['entity' => 'contribution', 'label' => ts('Cancelled / Refunded Date')], FALSE, FALSE); if ($this->_online) { $this->assign('hideCalender', TRUE); } - $checkNumber = $this->add('text', 'check_number', ts('Check Number'), $attributes['contribution_check_number']); - - $this->addDateTime('receipt_date', ts('Receipt Date'), FALSE, array('formatType' => 'activityDateTime')); - $this->addDateTime('cancel_date', ts('Cancelled / Refunded Date'), FALSE, array('formatType' => 'activityDateTime')); $this->add('textarea', 'cancel_reason', ts('Cancellation / Refund Reason'), $attributes['cancel_reason']); - $this->add('text', 'refund_trxn_id', ts('Transaction ID for the refund payment')); - $element = $this->add('select', - 'payment_processor_id', - ts('Payment Processor'), - $this->_processors, - NULL, - $recurJs - ); - - if ($this->_online) { - $element->freeze(); - } $totalAmount = NULL; if (empty($this->_lineItems)) { @@ -802,7 +729,6 @@ public function buildQuickForm() { // don't allow price set for contribution if it is related to participant, or if it is a pledge payment // and if we already have line items for that participant. CRM-5095 if ($buildPriceSet && $this->_id) { - $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); $pledgePaymentId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $this->_id, 'id', @@ -826,10 +752,10 @@ public function buildQuickForm() { // instead of selecting manually $financialTypeIds = CRM_Price_BAO_PriceSet::getAssoc(FALSE, 'CiviContribute', 'financial_type_id'); $element = $this->add('select', 'price_set_id', ts('Choose price set'), - array( + [ '' => ts('Choose price set'), - ) + $priceSets, - NULL, array('onchange' => "buildAmount( this.value, " . json_encode($financialTypeIds) . ");") + ] + $priceSets, + NULL, ['onchange' => "buildAmount( this.value, " . json_encode($financialTypeIds) . ");"] ); if ($this->_online && !($this->_action & CRM_Core_Action::UPDATE)) { $element->freeze(); @@ -838,18 +764,18 @@ public function buildQuickForm() { $this->assign('hasPriceSets', $hasPriceSets); if (!($this->_action & CRM_Core_Action::UPDATE)) { if ($this->_online || $this->_ppID) { - $attributes['total_amount'] = array_merge($attributes['total_amount'], array( + $attributes['total_amount'] = array_merge($attributes['total_amount'], [ 'READONLY' => TRUE, 'style' => "background-color:#EBECE4", - )); - $optionTypes = array( + ]); + $optionTypes = [ '1' => ts('Adjust Pledge Payment Schedule?'), '2' => ts('Adjust Total Pledge Amount?'), - ); + ]; $this->addRadio('option_type', NULL, $optionTypes, - array(), '
    ' + [], '
    ' ); $currencyFreeze = TRUE; @@ -875,50 +801,49 @@ public function buildQuickForm() { $js = NULL; if (!$this->_mode) { - $js = array('onclick' => "return verify( );"); + $js = ['onclick' => "return verify( );"]; } $mailingInfo = Civi::settings()->get('mailing_backend'); $this->assign('outBound_option', $mailingInfo['outBound_option']); - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'js' => $js, - 'isDefault' => TRUE, - ), - array( - 'type' => 'upload', - 'name' => ts('Save and New'), - 'js' => $js, - 'subName' => 'new', - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); - - // if status is Cancelled freeze Amount, Payment Instrument, Check #, Financial Type, - // Net and Fee Amounts are frozen in AdditionalInfo::buildAdditionalDetail - if ($this->_id && $this->_values['contribution_status_id'] == array_search('Cancelled', $statusName)) { - if ($totalAmount) { - $totalAmount->freeze(); - } - $checkNumber->freeze(); - $paymentInstrument->freeze(); - $trxnId->freeze(); - $financialType->freeze(); - } + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Save'), + 'js' => $js, + 'isDefault' => TRUE, + ], + [ + 'type' => 'upload', + 'name' => ts('Save and New'), + 'js' => $js, + 'subName' => 'new', + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); // if contribution is related to membership or participant freeze Financial Type, Amount - if ($this->_id && isset($this->_values['tax_amount'])) { + if ($this->_id) { $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); - if (CRM_Utils_Array::value('membership', $componentDetails) || CRM_Utils_Array::value('participant', $componentDetails)) { + $isCancelledStatus = ($this->_values['contribution_status_id'] == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Cancelled')); + + if (!empty($componentDetails['membership']) || + !empty($componentDetails['participant']) || + // if status is Cancelled freeze Amount, Payment Instrument, Check #, Financial Type, + // Net and Fee Amounts are frozen in AdditionalInfo::buildAdditionalDetail + $isCancelledStatus + ) { if ($totalAmount) { $totalAmount->freeze(); + $this->getElement('currency')->freeze(); + } + if ($isCancelledStatus) { + $paymentInstrument->freeze(); + $trxnId->freeze(); } $financialType->freeze(); $this->assign('freezeFinancialType', TRUE); @@ -943,7 +868,7 @@ public function buildQuickForm() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; // Check for Credit Card Contribution. if ($self->_mode) { if (empty($fields['payment_processor_id'])) { @@ -964,13 +889,6 @@ public static function formRule($fields, $files, $self) { $softErrors = CRM_Contribute_Form_SoftCredit::formRule($fields, $errors, $self); - if (!empty($fields['total_amount']) && (!empty($fields['net_amount']) || !empty($fields['fee_amount']))) { - $sum = CRM_Utils_Rule::cleanMoney($fields['net_amount']) + CRM_Utils_Rule::cleanMoney($fields['fee_amount']); - if (CRM_Utils_Rule::cleanMoney($fields['total_amount']) != $sum) { - $errors['total_amount'] = ts('The sum of fee amount and net amount must be equal to total amount'); - } - } - //CRM-16285 - Function to handle validation errors on form, for recurring contribution field. CRM_Contribute_BAO_ContributionRecur::validateRecurContribution($fields, $files, $self, $errors); @@ -995,15 +913,15 @@ public static function formRule($fields, $files, $self) { // $trxn_id must be unique CRM-13919 if (!empty($fields['trxn_id'])) { - $queryParams = array(1 => array($fields['trxn_id'], 'String')); + $queryParams = [1 => [$fields['trxn_id'], 'String']]; $query = 'select count(*) from civicrm_contribution where trxn_id = %1'; if ($self->_id) { - $queryParams[2] = array((int) $self->_id, 'Integer'); + $queryParams[2] = [(int) $self->_id, 'Integer']; $query .= ' and id !=%2'; } $tCnt = CRM_Core_DAO::singleValueQuery($query, $queryParams); if ($tCnt) { - $errors['trxn_id'] = ts('Transaction ID\'s must be unique. Transaction \'%1\' already exists in your database.', array(1 => $fields['trxn_id'])); + $errors['trxn_id'] = ts('Transaction ID\'s must be unique. Transaction \'%1\' already exists in your database.', [1 => $fields['trxn_id']]); } } if (!empty($fields['revenue_recognition_date']) @@ -1081,6 +999,18 @@ public function postProcess() { if (empty($this->_id) && !empty($contribution->id)) { $this->_id = $contribution->id; } + if (!empty($this->_id) && CRM_Core_Permission::access('CiviMember')) { + $membershipPaymentCount = civicrm_api3('MembershipPayment', 'getCount', ['contribution_id' => $this->_id]); + if ($membershipPaymentCount) { + $this->ajaxResponse['updateTabs']['#tab_member'] = CRM_Contact_BAO_Contact::getCountComponent('membership', $this->_contactID); + } + } + if (!empty($this->_id) && CRM_Core_Permission::access('CiviEvent')) { + $participantPaymentCount = civicrm_api3('ParticipantPayment', 'getCount', ['contribution_id' => $this->_id]); + if ($participantPaymentCount) { + $this->ajaxResponse['updateTabs']['#tab_participant'] = CRM_Contact_BAO_Contact::getCountComponent('participant', $this->_contactID); + } + } } /** @@ -1093,8 +1023,10 @@ public function postProcess() { * Contact ID * * @return bool|\CRM_Contribute_DAO_Contribution + * * @throws \CRM_Core_Exception * @throws \Civi\Payment\Exception\PaymentProcessorException + * @throws \CiviCRM_API3_Exception */ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $isTest = ($this->_mode == 'test') ? 1 : 0; @@ -1115,7 +1047,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, 'sort_name' ); - $submittedValues['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); + $submittedValues['source'] = ts('Submit Credit Card Payment by: %1', [1 => $userSortName]); } $params = $submittedValues; @@ -1126,14 +1058,6 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $now = date('YmdHis'); - // we need to retrieve email address - if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { - list($this->userDisplayName, - $this->userEmail - ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID); - $this->assign('displayName', $this->userDisplayName); - } - $this->_contributorEmail = $this->userEmail; $this->_contributorContactID = $contactID; $this->processBillingAddress(); @@ -1153,10 +1077,6 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { CRM_Core_Config::singleton()->defaultCurrency ); - if (!empty($this->_params['receive_date'])) { - $this->_params['receive_date'] = CRM_Utils_Date::processDate($this->_params['receive_date'], $this->_params['receive_date_time']); - } - $this->_params['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $params); $this->_params['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $params); $this->_params['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $params); @@ -1199,16 +1119,9 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $paymentParams['receive_date'] = $this->_params['receive_date']; } - $this->_params['receive_date'] = $now; - if (!empty($this->_params['is_email_receipt'])) { $this->_params['receipt_date'] = $now; } - else { - $this->_params['receipt_date'] = CRM_Utils_Date::processDate($this->_params['receipt_date'], - $params['receipt_date_time'], TRUE - ); - } $this->set('params', $this->_params); @@ -1220,7 +1133,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { $this->assign('is_deductible', TRUE); $this->set('is_deductible', TRUE); } - $contributionParams = array( + $contributionParams = [ 'id' => CRM_Utils_Array::value('contribution_id', $this->_params), 'contact_id' => $contactID, 'line_item' => $lineItem, @@ -1229,7 +1142,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { 'contribution_page_id' => CRM_Utils_Array::value('contribution_page_id', $this->_params), 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), 'thankyou_date' => CRM_Utils_Array::value('thankyou_date', $this->_params), - ); + ]; $contributionParams['payment_instrument_id'] = $this->_paymentProcessor['payment_instrument_id']; $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($this, @@ -1252,7 +1165,7 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { // NOTE - I expect this is obsolete. $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); try { - $statuses = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id'); + $completeStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); $result = $payment->doPayment($paymentParams, 'contribute'); $this->assign('trxn_id', $result['trxn_id']); $contribution->trxn_id = $result['trxn_id']; @@ -1266,9 +1179,9 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { * as historically we have had to guess from the context - ie doDirectPayment * = error or success, unless it is a recurring contribution in which case it is pending. */ - if ($result['payment_status_id'] == array_search('Completed', $statuses)) { + if ($result['payment_status_id'] == $completeStatusId) { try { - civicrm_api3('contribution', 'completetransaction', array( + civicrm_api3('contribution', 'completetransaction', [ 'id' => $contribution->id, 'trxn_id' => $result['trxn_id'], 'payment_processor_id' => $this->_paymentProcessor['id'], @@ -1276,7 +1189,8 @@ protected function processCreditCard($submittedValues, $lineItem, $contactID) { 'fee_amount' => CRM_Utils_Array::value('fee_amount', $result), 'card_type_id' => CRM_Utils_Array::value('card_type_id', $paymentParams), 'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $paymentParams), - )); + 'is_email_receipt' => FALSE, + ]); // This has now been set to 1 in the DB - declare it here also $contribution->contribution_status_id = 1; } @@ -1339,11 +1253,11 @@ protected function generatePane($type, $defaults) { $open = 'true'; } - $pane = array( + $pane = [ 'url' => CRM_Utils_System::url('civicrm/contact/view/contribution', $urlParams), 'open' => $open, 'id' => $type, - ); + ]; // See if we need to include this paneName in the current form. if ($this->_formType == $type || !empty($_POST["hidden_{$type}"]) || @@ -1375,27 +1289,36 @@ protected function generatePane($type, $defaults) { * @param int $action * @param string|null $creditCardMode * + * @return CRM_Contribute_BAO_Contribution + * + * @throws \CRM_Core_Exception * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException */ public function testSubmit($params, $action, $creditCardMode = NULL) { - $defaults = array( - 'soft_credit_contact_id' => array(), + $defaults = [ + 'soft_credit_contact_id' => [], + 'receive_date' => date('Y-m-d H:i:s'), 'receipt_date' => '', - 'receipt_date_time' => '', 'cancel_date' => '', - 'cancel_date_time' => '', 'hidden_Premium' => 1, - ); + ]; $this->_bltID = 5; if (!empty($params['id'])) { - $existingContribution = civicrm_api3('contribution', 'getsingle', array( + $existingContribution = civicrm_api3('contribution', 'getsingle', [ 'id' => $params['id'], - )); + ]); $this->_id = $params['id']; $this->_values = $existingContribution; + if (CRM_Contribute_BAO_Contribution::checkContributeSettings('invoicing')) { + $this->_values['tax_amount'] = civicrm_api3('contribution', 'getvalue', [ + 'id' => $params['id'], + 'return' => 'tax_amount', + ]); + } } else { - $existingContribution = array(); + $existingContribution = []; } $this->_defaults['contribution_status_id'] = CRM_Utils_Array::value('contribution_status_id', @@ -1416,8 +1339,8 @@ public function testSubmit($params, $action, $creditCardMode = NULL) { CRM_Contribute_Form_AdditionalInfo::buildPremium($this); - $this->_fields = array(); - $this->submit(array_merge($defaults, $params), $action, CRM_Utils_Array::value('pledge_payment_id', $params)); + $this->_fields = []; + return $this->submit(array_merge($defaults, $params), $action, CRM_Utils_Array::value('pledge_payment_id', $params)); } @@ -1430,8 +1353,11 @@ public function testSubmit($params, $action, $creditCardMode = NULL) { * * @param $pledgePaymentID * - * @return array - * @throws \Exception + * @return \CRM_Contribute_BAO_Contribution + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException */ protected function submit($submittedValues, $action, $pledgePaymentID) { $pId = $contribution = $isRelatedId = FALSE; @@ -1453,7 +1379,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { } // Process price set and get total amount and line items. - $lineItem = array(); + $lineItem = []; $priceSetId = CRM_Utils_Array::value('price_set_id', $submittedValues); if (empty($priceSetId) && !$this->_id) { $this->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name'); @@ -1469,7 +1395,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { // as a point of fragility rather than a logical 'if' clause. if ($priceSetId) { CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], - $submittedValues, $lineItem[$priceSetId]); + $submittedValues, $lineItem[$priceSetId], NULL, $priceSetId); // Unset tax amount for offline 'is_quick_config' contribution. // @todo WHY - quick config was conceived as a quick way to configure contribution forms. // this is an example of 'other' functionality being hung off it. @@ -1478,11 +1404,9 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { ) { unset($submittedValues['tax_amount']); } - // @todo - look to remove this line. I believe it relates to CRM-16460 - // and possibly contributes to fixing the issue described there but - // would cause breakage for negative values in some cases. $submittedValues['total_amount'] = CRM_Utils_Array::value('amount', $submittedValues); } + if ($this->_id) { if ($this->_compId) { if ($this->_context == 'participant') { @@ -1518,10 +1442,10 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { $entityTable = 'participant'; $entityID = $pId; $isRelatedId = FALSE; - $participantParams = array( + $participantParams = [ 'fee_amount' => $submittedValues['total_amount'], 'id' => $entityID, - ); + ]; CRM_Event_BAO_Participant::add($participantParams); if (empty($this->_lineItems)) { $this->_lineItems[] = CRM_Price_BAO_LineItem::getLineItems($entityID, 'participant', TRUE); @@ -1591,13 +1515,17 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { } if (!isset($submittedValues['total_amount'])) { - $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values) - CRM_Utils_Array::value('tax_amount', $this->_values); + $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values); + // Avoid tax amount deduction on edit form and keep it original, because this will lead to error described in CRM-20676 + if (!$this->_id) { + $submittedValues['total_amount'] -= CRM_Utils_Array::value('tax_amount', $this->_values, 0); + } } $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE); $isEmpty = array_keys(array_flip($submittedValues['soft_credit_contact_id'])); if ($this->_id && count($isEmpty) == 1 && key($isEmpty) == NULL) { - civicrm_api3('ContributionSoft', 'get', array('contribution_id' => $this->_id, 'pcp_id' => NULL, 'api.ContributionSoft.delete' => 1)); + civicrm_api3('ContributionSoft', 'get', ['contribution_id' => $this->_id, 'pcp_id' => NULL, 'api.ContributionSoft.delete' => 1]); } // set the contact, when contact is selected @@ -1609,13 +1537,13 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { // Credit Card Contribution. if ($this->_mode) { - $paramsSetByPaymentProcessingSubsystem = array( + $paramsSetByPaymentProcessingSubsystem = [ 'trxn_id', 'payment_instrument_id', 'contribution_status_id', 'cancel_date', 'cancel_reason', - ); + ]; foreach ($paramsSetByPaymentProcessingSubsystem as $key) { if (isset($formValues[$key])) { unset($formValues[$key]); @@ -1632,16 +1560,18 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { // get the required field value only. - $params = $ids = array(); - - $params['contact_id'] = $this->_contactID; - $params['currency'] = $this->getCurrency($submittedValues); + $params = [ + 'contact_id' => $this->_contactID, + 'currency' => $this->getCurrency($submittedValues), + 'skipCleanMoney' => TRUE, + 'id' => $this->_id, + ]; //format soft-credit/pcp param first CRM_Contribute_BAO_ContributionSoft::formatSoftCreditParams($submittedValues, $this); $params = array_merge($params, $submittedValues); - $fields = array( + $fields = [ 'financial_type_id', 'contribution_status_id', 'payment_instrument_id', @@ -1650,15 +1580,11 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { 'check_number', 'card_type_id', 'pan_truncation', - ); + ]; foreach ($fields as $f) { $params[$f] = CRM_Utils_Array::value($f, $formValues); } - // CRM-5740 if priceset is used, no need to cleanup money. - if ($priceSetId) { - $params['skipCleanMoney'] = 1; - } $params['revenue_recognition_date'] = NULL; if (!empty($formValues['revenue_recognition_date']) && count(array_filter($formValues['revenue_recognition_date'])) == 2 @@ -1667,17 +1593,6 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { '01-' . implode('-', $formValues['revenue_recognition_date']) ); } - $dates = array( - 'receive_date', - 'receipt_date', - 'cancel_date', - ); - - foreach ($dates as $d) { - if (isset($formValues[$d])) { - $params[$d] = CRM_Utils_Date::processDate($formValues[$d], CRM_Utils_Array::value($d . '_time', $formValues), TRUE); - } - } if (!empty($formValues['is_email_receipt'])) { $params['receipt_date'] = date("Y-m-d"); @@ -1702,8 +1617,6 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { $params['is_pay_later'] = 0; } - $ids['contribution'] = $params['id'] = $this->_id; - // Add Additional common information to formatted params. CRM_Contribute_Form_AdditionalInfo::postProcessCommon($formValues, $params, $this); if ($pId) { @@ -1716,9 +1629,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { } $params['line_item'] = $lineItem; $params['payment_processor_id'] = $params['payment_processor'] = CRM_Utils_Array::value('id', $this->_paymentProcessor); - if (isset($submittedValues['tax_amount'])) { - $params['tax_amount'] = $submittedValues['tax_amount']; - } + $params['tax_amount'] = CRM_Utils_Array::value('tax_amount', $submittedValues, CRM_Utils_Array::value('tax_amount', $this->_values)); //create contribution. if ($isQuickConfig) { $params['is_quick_config'] = 1; @@ -1729,7 +1640,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { if (!empty($params['note']) && !empty($submittedValues['note'])) { unset($params['note']); } - $contribution = CRM_Contribute_BAO_Contribution::create($params, $ids); + $contribution = CRM_Contribute_BAO_Contribution::create($params); // process associated membership / participant, CRM-4395 if ($contribution->id && $action & CRM_Core_Action::UPDATE) { @@ -1754,7 +1665,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { $formValues += CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id); // to get 'from email id' for send receipt - $this->fromEmailId = $formValues['from_email_address']; + $this->fromEmailId = CRM_Utils_Array::value('from_email_address', $formValues); if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $formValues)) { $this->statusMessage[] = ts('A receipt has been emailed to the contributor.'); } @@ -1770,7 +1681,7 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { ); } - if ($contribution->id && !empty($submittedValues['note'])) { + if ($contribution->id && array_key_exists('note', $submittedValues)) { CRM_Contribute_Form_AdditionalInfo::processNote($submittedValues, $this->_contactID, $contribution->id, $this->_noteID); } @@ -1799,10 +1710,10 @@ protected function submit($submittedValues, $action, $pledgePaymentID) { protected function invoicingPostProcessHook($submittedValues, $action, $lineItem) { $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - if (!CRM_Utils_Array::value('invoicing', $invoiceSettings)) { + if (empty($invoiceSettings['invoicing'])) { return; } - $taxRate = array(); + $taxRate = []; $getTaxDetails = FALSE; foreach ($lineItem as $key => $value) { diff --git a/CRM/Contribute/Form/Contribution/Confirm.php b/CRM/Contribute/Form/Contribution/Confirm.php index 419f94912938..a977f45dde3d 100644 --- a/CRM/Contribute/Form/Contribution/Confirm.php +++ b/CRM/Contribute/Form/Contribution/Confirm.php @@ -1,9 +1,9 @@ $dontCare) { $scheduledAmount = CRM_Core_DAO::getFieldValue( 'CRM_Pledge_DAO_PledgePayment', @@ -76,12 +79,12 @@ public static function handlePledge(&$form, $params, $contributionParams, $pledg $pledgePayment = ($amount >= $scheduledAmount) ? $scheduledAmount : $amount; if ($pledgePayment > 0) { - $pledgePaymentParams[] = array( + $pledgePaymentParams[] = [ 'id' => $paymentId, 'contribution_id' => $contribution->id, 'status_id' => $contribution->contribution_status_id, 'actual_amount' => $pledgePayment, - ); + ]; $amount -= $pledgePayment; } } @@ -98,7 +101,7 @@ public static function handlePledge(&$form, $params, $contributionParams, $pledg } else { //when user creating pledge record. - $pledgeParams = array(); + $pledgeParams = []; $pledgeParams['contact_id'] = $contribution->contact_id; $pledgeParams['installment_amount'] = $pledgeParams['actual_amount'] = $contribution->total_amount; $pledgeParams['contribution_id'] = $contribution->id; @@ -114,7 +117,7 @@ public static function handlePledge(&$form, $params, $contributionParams, $pledg $pledgeParams['frequency_day'] = 1; } $pledgeParams['create_date'] = $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = date("Ymd"); - if (CRM_Utils_Array::value('start_date', $params)) { + if (!empty($params['start_date'])) { $pledgeParams['frequency_day'] = intval(date("d", strtotime(CRM_Utils_Array::value('start_date', $params)))); $pledgeParams['start_date'] = $pledgeParams['scheduled_date'] = date('Ymd', strtotime(CRM_Utils_Array::value('start_date', $params))); } @@ -158,8 +161,6 @@ public static function handlePledge(&$form, $params, $contributionParams, $pledg * * @param array $params * @param int $financialTypeID - * @param float $nonDeductibleAmount - * @param bool $pending * @param array $paymentProcessorOutcome * @param string $receiptDate * @param int $recurringContributionID @@ -167,13 +168,11 @@ public static function handlePledge(&$form, $params, $contributionParams, $pledg * @return array */ public static function getContributionParams( - $params, $financialTypeID, $nonDeductibleAmount, $pending, + $params, $financialTypeID, $paymentProcessorOutcome, $receiptDate, $recurringContributionID) { - $contributionParams = array( + $contributionParams = [ 'financial_type_id' => $financialTypeID, 'receive_date' => (CRM_Utils_Array::value('receive_date', $params)) ? CRM_Utils_Date::processDate($params['receive_date']) : date('YmdHis'), - 'non_deductible_amount' => $nonDeductibleAmount, - 'total_amount' => $params['amount'], 'tax_amount' => CRM_Utils_Array::value('tax_amount', $params), 'amount_level' => CRM_Utils_Array::value('amount_level', $params), 'invoice_id' => $params['invoiceID'], @@ -186,31 +185,22 @@ public static function getContributionParams( 'thankyou_date' => isset($params['thankyou_date']) ? CRM_Utils_Date::format($params['thankyou_date']) : NULL, //setting to make available to hook - although seems wrong to set on form for BAO hook availability 'skipLineItem' => CRM_Utils_Array::value('skipLineItem', $params, 0), - ); + ]; if ($paymentProcessorOutcome) { $contributionParams['payment_processor'] = CRM_Utils_Array::value('payment_processor', $paymentProcessorOutcome); } - if (!$pending && $paymentProcessorOutcome) { - $contributionParams += array( - 'fee_amount' => CRM_Utils_Array::value('fee_amount', $paymentProcessorOutcome), - 'net_amount' => CRM_Utils_Array::value('net_amount', $paymentProcessorOutcome, $params['amount']), - 'trxn_id' => $paymentProcessorOutcome['trxn_id'], + if (!empty($params["is_email_receipt"])) { + $contributionParams += [ 'receipt_date' => $receiptDate, - // also add financial_trxn details as part of fix for CRM-4724 - 'trxn_result_code' => CRM_Utils_Array::value('trxn_result_code', $paymentProcessorOutcome), - ); + ]; } - // CRM-4038: for non-en_US locales, CRM_Contribute_BAO_Contribution::add() expects localised amounts - $contributionParams['non_deductible_amount'] = trim(CRM_Utils_Money::format($contributionParams['non_deductible_amount'], ' ')); - $contributionParams['total_amount'] = trim(CRM_Utils_Money::format($contributionParams['total_amount'], ' ')); - if ($recurringContributionID) { $contributionParams['contribution_recur_id'] = $recurringContributionID; } - $contributionParams['contribution_status_id'] = $pending ? 2 : 1; + $contributionParams['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); if (isset($contributionParams['invoice_id'])) { $contributionParams['id'] = CRM_Core_DAO::getFieldValue( 'CRM_Contribute_DAO_Contribution', @@ -293,13 +283,12 @@ protected static function getNonDeductibleAmount($params, $financialType, $onlin * Set variables up before form is built. */ public function preProcess() { - $config = CRM_Core_Config::singleton(); parent::preProcess(); // lineItem isn't set until Register postProcess $this->_lineItem = $this->get('lineItem'); $this->_ccid = $this->get('ccid'); - $this->_paymentProcessor = $this->get('paymentProcessor'); + $this->_params = $this->controller->exportValues('Main'); $this->_params['ip_address'] = CRM_Utils_System::ipAddress(); $this->_params['amount'] = $this->get('amount'); @@ -315,7 +304,7 @@ public function preProcess() { $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params); } - $this->_params['currencyID'] = $config->defaultCurrency; + $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency; if (!empty($this->_membershipBlock)) { $this->_params['selectMembership'] = $this->get('selectMembership'); @@ -336,9 +325,11 @@ public function preProcess() { } // if onbehalf-of-organization if (!empty($this->_values['onbehalf_profile_id']) && !empty($this->_params['onbehalf']['organization_name'])) { - $this->_params['organization_id'] = CRM_Utils_Array::value('onbehalfof_id', $this->_params); + if (empty($this->_params['org_option']) && empty($this->_params['organization_id'])) { + $this->_params['organization_id'] = CRM_Utils_Array::value('onbehalfof_id', $this->_params); + } $this->_params['organization_name'] = $this->_params['onbehalf']['organization_name']; - $addressBlocks = array( + $addressBlocks = [ 'street_address', 'city', 'state_province', @@ -351,9 +342,9 @@ public function preProcess() { 'geo_code_1', 'geo_code_2', 'address_name', - ); + ]; - $blocks = array('email', 'phone', 'im', 'url', 'openid'); + $blocks = ['email', 'phone', 'im', 'url', 'openid']; foreach ($this->_params['onbehalf'] as $loc => $value) { $field = $typeId = NULL; if (strstr($loc, '-')) { @@ -398,7 +389,7 @@ public function preProcess() { $locationValue = $locType; } $locTypeId = ''; - $phoneExtField = array(); + $phoneExtField = []; if ($field == 'url') { $blockName = 'website'; @@ -417,25 +408,25 @@ public function preProcess() { //check if extension field exists $extField = str_replace('phone', 'phone_ext', $loc); if (isset($this->_params['onbehalf'][$extField])) { - $phoneExtField = array('phone_ext' => $this->_params['onbehalf'][$extField]); + $phoneExtField = ['phone_ext' => $this->_params['onbehalf'][$extField]]; } } $isPrimary = 1; - if (isset ($this->_params['onbehalf_location'][$blockName]) + if (isset($this->_params['onbehalf_location'][$blockName]) && count($this->_params['onbehalf_location'][$blockName]) > 0 ) { $isPrimary = 0; } if ($locationValue) { - $blockValues = array( + $blockValues = [ $fieldName => $value, $locationType => $locationValue, 'is_primary' => $isPrimary, - ); + ]; if ($locTypeId) { - $blockValues = array_merge($blockValues, array($locTypeId => $typeId)); + $blockValues = array_merge($blockValues, [$locTypeId => $typeId]); } if (!empty($phoneExtField)) { $blockValues = array_merge($blockValues, $phoneExtField); @@ -464,11 +455,11 @@ public function preProcess() { elseif (!empty($this->_values['is_for_organization'])) { // no on behalf of an organization, CRM-5519 // so reset loc blocks from main params. - foreach (array( - 'phone', - 'email', - 'address', - ) as $blk) { + foreach ([ + 'phone', + 'email', + 'address', + ] as $blk) { if (isset($this->_params[$blk])) { unset($this->_params[$blk]); } @@ -498,6 +489,7 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { + // FIXME: Some of this code is identical to Thankyou.php and should be broken out into a shared function $this->assignToTemplate(); $params = $this->_params; @@ -509,7 +501,7 @@ public function buildQuickForm() { $this->assign('soft_credit_type', $softCreditTypes[$params['soft_credit_type_id']]); CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields($this, $params['honor']); - $fieldTypes = array('Contact'); + $fieldTypes = ['Contact']; $fieldTypes[] = CRM_Core_BAO_UFGroup::getContactType($this->_values['honoree_profile_id']); $this->buildCustom($this->_values['honoree_profile_id'], 'honoreeProfileFields', TRUE, 'honor', $fieldTypes); } @@ -517,24 +509,27 @@ public function buildQuickForm() { $amount_block_is_active = $this->get('amount_block_is_active'); $this->assign('amount_block_is_active', $amount_block_is_active); - $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); - if ($invoicing) { - $getTaxDetails = FALSE; - $taxTerm = CRM_Utils_Array::value('tax_term', $invoiceSettings); - foreach ($this->_lineItem as $key => $value) { - foreach ($value as $v) { - if (isset($v['tax_rate'])) { - if ($v['tax_rate'] != '') { - $getTaxDetails = TRUE; - } - } - } - } - $this->assign('getTaxDetails', $getTaxDetails); - $this->assign('taxTerm', $taxTerm); + // Make a copy of line items array to use for display only + $tplLineItems = $this->_lineItem; + if (CRM_Invoicing_Utils::isInvoicingEnabled()) { + // @todo $params seems like exactly the wrong place to get totalTaxAmount from + // this is a calculated variable so we it should be transparent how we + // calculated it rather than coming from 'params' $this->assign('totalTaxAmount', $params['tax_amount']); } + $this->assignLineItemsToTemplate($tplLineItems); + + $isDisplayLineItems = $this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config'); + + $this->assign('isDisplayLineItems', $isDisplayLineItems); + + if (!$isDisplayLineItems) { + // quickConfig is deprecated in favour of isDisplayLineItems. Lots of logic has been harnessed to quick config + // whereas isDisplayLineItems is specific & clear. + $this->assign('is_quick_config', 1); + $this->_params['is_quick_config'] = 1; + } + if (!empty($params['selectProduct']) && $params['selectProduct'] != 'no_thanks') { $option = CRM_Utils_Array::value('options_' . $params['selectProduct'], $params); $productID = $params['selectProduct']; @@ -574,14 +569,14 @@ public function buildQuickForm() { !empty($params['is_for_organization']) ) && empty($this->_ccid) ) { - $fieldTypes = array('Contact', 'Organization'); + $fieldTypes = ['Contact', 'Organization']; $contactSubType = CRM_Contact_BAO_ContactType::subTypes('Organization'); $fieldTypes = array_merge($fieldTypes, $contactSubType); if (is_array($this->_membershipBlock) && !empty($this->_membershipBlock)) { - $fieldTypes = array_merge($fieldTypes, array('Membership')); + $fieldTypes = array_merge($fieldTypes, ['Membership']); } else { - $fieldTypes = array_merge($fieldTypes, array('Contribution')); + $fieldTypes = array_merge($fieldTypes, ['Contribution']); } $this->buildCustom($this->_values['onbehalf_profile_id'], 'onbehalfProfile', TRUE, 'onbehalf', $fieldTypes); @@ -589,49 +584,46 @@ public function buildQuickForm() { $this->_separateMembershipPayment = $this->get('separateMembershipPayment'); $this->assign('is_separate_payment', $this->_separateMembershipPayment); - if ($this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { - $this->assign('lineItem', $this->_lineItem); - } - else { - $this->assign('is_quick_config', 1); - $this->_params['is_quick_config'] = 1; - } + $this->assign('priceSetID', $this->_priceSetId); // The concept of contributeMode is deprecated. // the is_monetary concept probably should be too as it can be calculated from // the existence of 'amount' & seems fragile. if ($this->_contributeMode == 'notify' || - $this->_amount < 0.0 || $this->_params['is_pay_later'] || - ($this->_separateMembershipPayment && $this->_amount <= 0.0) + $this->_amount <= 0.0 || $this->_params['is_pay_later'] ) { $contribButton = ts('Continue'); - $this->assign('button', ts('Continue')); } elseif (!empty($this->_ccid)) { $contribButton = ts('Make Payment'); - $this->assign('button', ts('Make Payment')); } else { $contribButton = ts('Make Contribution'); - $this->assign('button', ts('Make Contribution')); - } - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => $contribButton, - 'spacing' => '         ', - 'isDefault' => TRUE, - 'js' => array('onclick' => "return submitOnce(this,'" . $this->_name . "','" . ts('Processing') . "');"), - ), - array( - 'type' => 'back', - 'name' => ts('Go Back'), - ), - ) + } + $this->assign('button', $contribButton); + + $this->assign('continueText', + $this->getPaymentProcessorObject()->getText('contributionPageContinueText', [ + 'is_payment_to_existing' => !empty($this->_ccid), + 'amount' => $this->_amount, + ]) ); - $defaults = array(); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => $contribButton, + 'spacing' => '         ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Go Back'), + ], + ]); + + $defaults = []; $fields = array_fill_keys(array_keys($this->_fields), 1); $fields["billing_state_province-{$this->_bltID}"] = $fields["billing_country-{$this->_bltID}"] = $fields["email-{$this->_bltID}"] = 1; @@ -640,10 +632,10 @@ public function buildQuickForm() { // Recursively set defaults for nested fields if (isset($contact[$name]) && is_array($contact[$name]) && ($name == 'onbehalf' || $name == 'honor')) { foreach ($contact[$name] as $fieldName => $fieldValue) { - if (is_array($fieldValue) && !in_array($this->_fields[$name][$fieldName]['html_type'], array( - 'Multi-Select', - 'AdvMulti-Select', - )) + if (is_array($fieldValue) && !in_array($this->_fields[$name][$fieldName]['html_type'], [ + 'Multi-Select', + 'AdvMulti-Select', + ]) ) { foreach ($fieldValue as $key => $value) { $defaults["{$name}[{$fieldName}][{$key}]"] = $value; @@ -665,11 +657,11 @@ public function buildQuickForm() { $defaults["{$name}_id"] = $contact["{$name}_id"]; } } - elseif (in_array($name, array( - 'addressee', - 'email_greeting', - 'postal_greeting', - )) && !empty($contact[$name . '_custom']) + elseif (in_array($name, [ + 'addressee', + 'email_greeting', + 'postal_greeting', + ]) && !empty($contact[$name . '_custom']) ) { $defaults[$name . '_custom'] = $contact[$name . '_custom']; } @@ -713,14 +705,15 @@ public function setDefaultValues() { */ public function postProcess() { $contactID = $this->getContactID(); - $result = $this->processFormSubmission($contactID); + try { + $result = $this->processFormSubmission($contactID); + } + catch (CRM_Core_Exception $e) { + $this->bounceOnError($e->getMessage()); + } + if (is_array($result) && !empty($result['is_payment_failure'])) { - // We will probably have the function that gets this error throw an exception on the next round of refactoring. - CRM_Core_Session::singleton()->setStatus(ts("Payment Processor Error message :") . - $result['error']->getMessage()); - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', - "_qf_Main_display=true&qfKey={$this->_params['qfKey']}" - )); + $this->bounceOnError($result['error']->getMessage()); } // Presumably this is for hooks to access? Not quite clear & perhaps not required. $this->set('params', $this->_params); @@ -734,21 +727,18 @@ public function postProcess() { * * Comments from previous refactor indicate doubt as to what was going on. * - * @param int $contributionTypeId + * @param int $financialTypeID * * @return null|string */ - public function wrangleFinancialTypeID($contributionTypeId) { - if (isset($paymentParams['financial_type'])) { - $contributionTypeId = $paymentParams['financial_type']; - } - elseif (!empty($this->_values['pledge_id'])) { - $contributionTypeId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', + public function wrangleFinancialTypeID($financialTypeID) { + if (empty($financialTypeID) && !empty($this->_values['pledge_id'])) { + $financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $this->_values['pledge_id'], 'financial_type_id' ); } - return $contributionTypeId; + return $financialTypeID; } /** @@ -830,14 +820,14 @@ protected function postProcessPremium($premiumParams, $contribution) { $this->assign('contact_email', $dao->premiums_contact_email); //create Premium record - $params = array( + $params = [ 'product_id' => $premiumParams['selectProduct'], 'contribution_id' => $contribution->id, 'product_option' => CRM_Utils_Array::value('options_' . $premiumParams['selectProduct'], $premiumParams), 'quantity' => 1, 'start_date' => CRM_Utils_Date::customFormat($startDate, '%Y%m%d'), 'end_date' => CRM_Utils_Date::customFormat($endDate, '%Y%m%d'), - ); + ]; if (!empty($premiumParams['selectProduct'])) { $daoPremiumsProduct = new CRM_Contribute_DAO_PremiumsProduct(); $daoPremiumsProduct->product_id = $premiumParams['selectProduct']; @@ -854,12 +844,12 @@ protected function postProcessPremium($premiumParams, $contribution) { CRM_Contribute_BAO_Contribution::addPremium($params); if ($productDAO->cost && !empty($params['financial_type_id'])) { - $trxnParams = array( + $trxnParams = [ 'cost' => $productDAO->cost, 'currency' => $productDAO->currency, 'financial_type_id' => $params['financial_type_id'], 'contributionId' => $contribution->id, - ); + ]; CRM_Core_BAO_FinancialTrxn::createPremiumTrxn($trxnParams); } } @@ -902,7 +892,9 @@ protected function postProcessPremium($premiumParams, $contribution) { * Is this recurring? * * @return \CRM_Contribute_DAO_Contribution - * @throws \Exception + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public static function processFormContribution( &$form, @@ -943,9 +935,8 @@ public static function processFormContribution( $params['is_email_receipt'] = $isEmailReceipt; } $params['is_recur'] = $isRecur; - $params['payment_instrument_id'] = $contributionParams['payment_instrument_id']; + $params['payment_instrument_id'] = CRM_Utils_Array::value('payment_instrument_id', $contributionParams); $recurringContributionID = self::processRecurringContribution($form, $params, $contactID, $financialType); - $nonDeductibleAmount = self::getNonDeductibleAmount($params, $financialType, $online, $form); $now = date('YmdHis'); $receiptDate = CRM_Utils_Array::value('receipt_date', $params); @@ -955,16 +946,22 @@ public static function processFormContribution( if (isset($params['amount'])) { $contributionParams = array_merge(self::getContributionParams( - $params, $financialType->id, $nonDeductibleAmount, TRUE, + $params, $financialType->id, $result, $receiptDate, $recurringContributionID), $contributionParams ); + $contributionParams['non_deductible_amount'] = self::getNonDeductibleAmount($params, $financialType, $online, $form); + $contributionParams['skipCleanMoney'] = TRUE; + // @todo this is the wrong place for this - it should be done as close to form submission + // as possible + $contributionParams['total_amount'] = $params['amount']; + $contribution = CRM_Contribute_BAO_Contribution::add($contributionParams); $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); if ($invoicing) { - $dataArray = array(); + $dataArray = []; // @todo - interrogate the line items passed in on the params array. // No reason to assume line items will be set on the form. foreach ($form->_lineItem as $lineItemKey => $lineItemValue) { @@ -983,10 +980,6 @@ public static function processFormContribution( $smarty->assign('dataArray', $dataArray); $smarty->assign('totalTaxAmount', $params['tax_amount']); } - if (is_a($contribution, 'CRM_Core_Error')) { - $message = CRM_Core_Error::getMessages($contribution); - CRM_Core_Error::fatal($message); - } // lets store it in the form variable so postProcess hook can get to this and use it $form->_contributionID = $contribution->id; @@ -1013,23 +1006,22 @@ public static function processFormContribution( //handle custom data. $params['contribution_id'] = $contribution->id; if (!empty($params['custom']) && - is_array($params['custom']) && - !is_a($contribution, 'CRM_Core_Error') + is_array($params['custom']) ) { CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id); } } // Save note if ($contribution && !empty($params['contribution_note'])) { - $noteParams = array( + $noteParams = [ 'entity_table' => 'civicrm_contribution', 'note' => $params['contribution_note'], 'entity_id' => $contribution->id, 'contact_id' => $contribution->contact_id, 'modified_date' => date('Ymd'), - ); + ]; - CRM_Core_BAO_Note::add($noteParams, array()); + CRM_Core_BAO_Note::add($noteParams, []); } if (isset($params['related_contact'])) { @@ -1078,7 +1070,7 @@ public static function processRecurringContribution(&$form, &$params, $contactID return NULL; } - $recurParams = array('contact_id' => $contactID); + $recurParams = ['contact_id' => $contactID]; $recurParams['amount'] = CRM_Utils_Array::value('amount', $params); $recurParams['auto_renew'] = CRM_Utils_Array::value('auto_renew', $params); $recurParams['frequency_unit'] = CRM_Utils_Array::value('frequency_unit', $params); @@ -1165,7 +1157,7 @@ public static function processRecurringContribution(&$form, &$params, $contactID */ public static function processOnBehalfOrganization(&$behalfOrganization, &$contactID, &$values, &$params, $fields = NULL) { $isNotCurrentEmployer = FALSE; - $dupeIDs = array(); + $dupeIDs = []; $orgID = NULL; if (!empty($behalfOrganization['organization_id'])) { $orgID = $behalfOrganization['organization_id']; @@ -1189,11 +1181,11 @@ public static function processOnBehalfOrganization(&$behalfOrganization, &$conta if (!$orgID) { // check if matching organization contact exists - $dupeID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($behalfOrganization, 'Organization', 'Unsupervised', array(), FALSE); + $dupeIDs = CRM_Contact_BAO_Contact::getDuplicateContacts($behalfOrganization, 'Organization', 'Unsupervised', [], FALSE); // CRM-6243 says to pick the first org even if more than one match if (count($dupeIDs) >= 1) { - $behalfOrganization['contact_id'] = $orgID = $dupeID; + $behalfOrganization['contact_id'] = $orgID = $dupeIDs[0]; // don't allow name edit unset($behalfOrganization['organization_name']); } @@ -1217,7 +1209,7 @@ public static function processOnBehalfOrganization(&$behalfOrganization, &$conta // create relationship if ($isNotCurrentEmployer) { $relParams['contact_check'][$orgID] = 1; - $cid = array('contact' => $contactID); + $cid = ['contact' => $contactID]; CRM_Contact_BAO_Relationship::legacyCreateMultiple($relParams, $cid); } @@ -1270,14 +1262,15 @@ public static function processOnBehalfOrganization(&$behalfOrganization, &$conta * Contribution object. */ public static function pcpNotifyOwner($contribution, $contributionSoft) { - $params = array('id' => $contributionSoft->pcp_id); + $params = ['id' => $contributionSoft->pcp_id]; CRM_Core_DAO::commonRetrieve('CRM_PCP_DAO_PCP', $params, $pcpInfo); $ownerNotifyID = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCPBlock', $pcpInfo['pcp_block_id'], 'owner_notify_id'); + $ownerNotifyOption = CRM_Core_PseudoConstant::getName('CRM_PCP_DAO_PCPBlock', 'owner_notify_id', $ownerNotifyID); - if ($ownerNotifyID != CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'no_notifications', 'name') && - (($ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'owner_chooses', 'name') && + if ($ownerNotifyOption != 'no_notifications' && + (($ownerNotifyOption == 'owner_chooses' && CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP', $contributionSoft->pcp_id, 'is_notify')) || - $ownerNotifyID == CRM_Core_OptionGroup::getValue('pcp_owner_notify', 'all_owners', 'name'))) { + $ownerNotifyOption == 'all_owners')) { $pcpInfoURL = CRM_Utils_System::url('civicrm/pcp/info', "reset=1&id={$contributionSoft->pcp_id}", TRUE, NULL, FALSE, TRUE @@ -1293,7 +1286,7 @@ public static function pcpNotifyOwner($contribution, $contributionSoft) { list($donorName, $email) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contribution->contact_id); } list($ownerName, $ownerEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contributionSoft->contact_id); - $tplParams = array( + $tplParams = [ 'page_title' => $pcpInfo['title'], 'receive_date' => $contribution->receive_date, 'total_amount' => $contributionSoft->amount, @@ -1302,9 +1295,9 @@ public static function pcpNotifyOwner($contribution, $contributionSoft) { 'pcpInfoURL' => $pcpInfoURL, 'is_honor_roll_enabled' => $contributionSoft->pcp_display_in_roll, 'currency' => $contributionSoft->currency, - ); + ]; $domainValues = CRM_Core_BAO_Domain::getNameAndEmail(); - $sendTemplateParams = array( + $sendTemplateParams = [ 'groupName' => 'msg_tpl_workflow_contribution', 'valueName' => 'pcp_owner_notify', 'contactId' => $contributionSoft->contact_id, @@ -1313,7 +1306,7 @@ public static function pcpNotifyOwner($contribution, $contributionSoft) { 'from' => "$domainValues[0] <$domainValues[1]>", 'tplParams' => $tplParams, 'PDFFilename' => 'receipt.pdf', - ); + ]; CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); } } @@ -1339,12 +1332,12 @@ public static function processPcp(&$page, $params) { else { $params['pcp_is_anonymous'] = 0; } - foreach (array( - 'pcp_display_in_roll', - 'pcp_is_anonymous', - 'pcp_roll_nickname', - 'pcp_personal_note', - ) as $val) { + foreach ([ + 'pcp_display_in_roll', + 'pcp_is_anonymous', + 'pcp_roll_nickname', + 'pcp_personal_note', + ] as $val) { if (!empty($params[$val])) { $page->assign($val, $params[$val]); } @@ -1369,8 +1362,7 @@ protected function processMembership($membershipParams, $contactID, $customField $membershipTypeIDs = (array) $membershipParams['selectMembership']; $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs); - $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes); - $isPending = $this->getIsPending(); + $membershipType = empty($membershipTypes) ? [] : reset($membershipTypes); $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType)); $this->_values['membership_name'] = CRM_Utils_Array::value('name', $membershipType); @@ -1398,7 +1390,7 @@ protected function processMembership($membershipParams, $contactID, $customField $this->postProcessMembership($membershipParams, $contactID, $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $financialTypeID, - $membershipLineItems, $isPending); + $membershipLineItems); $this->assign('membership_assign', TRUE); $this->set('membershipTypeID', $membershipParams['selectMembership']); @@ -1430,20 +1422,21 @@ protected function processMembership($membershipParams, $contactID, $customField * @param int $financialTypeID * @param array $unprocessedLineItems * Line items for payment options chosen on the form. - * @param bool $isPending * * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException */ protected function postProcessMembership( $membershipParams, $contactID, &$form, $premiumParams, $customFieldsFormatted = NULL, $includeFieldTypes = NULL, $membershipDetails, $membershipTypeIDs, $isPaidMembership, $membershipID, - $isProcessSeparateMembershipTransaction, $financialTypeID, $unprocessedLineItems, $isPending) { + $isProcessSeparateMembershipTransaction, $financialTypeID, $unprocessedLineItems) { $membershipContribution = NULL; $isTest = CRM_Utils_Array::value('is_test', $membershipParams, FALSE); - $errors = $paymentResults = array(); + $errors = $paymentResults = []; $form->_values['isMembership'] = TRUE; - $isRecurForFirstTransaction = CRM_Utils_Array::value('is_recur', $form->_values, CRM_Utils_Array::value('is_recur', $membershipParams)); + $isRecurForFirstTransaction = CRM_Utils_Array::value('is_recur', $form->_params, CRM_Utils_Array::value('is_recur', $membershipParams)); $totalAmount = $membershipParams['amount']; @@ -1476,7 +1469,7 @@ protected function postProcessMembership( $isRecurForFirstTransaction ); if (!empty($paymentResult['contribution'])) { - $paymentResults[] = array('contribution_id' => $paymentResult['contribution']->id, 'result' => $paymentResult); + $paymentResults[] = ['contribution_id' => $paymentResult['contribution']->id, 'result' => $paymentResult]; $this->postProcessPremium($premiumParams, $paymentResult['contribution']); //note that this will be over-written if we are using a separate membership transaction. Otherwise there is only one $membershipContribution = $paymentResult['contribution']; @@ -1492,9 +1485,10 @@ protected function postProcessMembership( if (empty($form->_params['auto_renew']) && !empty($membershipParams['is_recur'])) { unset($membershipParams['is_recur']); } - list($membershipContribution, $secondPaymentResult) = $this->processSecondaryFinancialTransaction($contactID, $form, array_merge($membershipParams, array('skipLineItem' => 1)), + list($membershipContribution, $secondPaymentResult) = $this->processSecondaryFinancialTransaction($contactID, $form, array_merge($membershipParams, ['skipLineItem' => 1]), $isTest, $unprocessedLineItems, CRM_Utils_Array::value('minimum_fee', $membershipDetails, 0), CRM_Utils_Array::value('financial_type_id', $membershipDetails)); - $paymentResults[] = array('contribution_id' => $membershipContribution->id, 'result' => $secondPaymentResult); + $paymentResults[] = ['contribution_id' => $membershipContribution->id, 'result' => $secondPaymentResult]; + $totalAmount = $membershipContribution->total_amount; } catch (CRM_Core_Exception $e) { $errors[2] = $e->getMessage(); @@ -1513,9 +1507,9 @@ protected function postProcessMembership( } //@todo it should no longer be possible for it to get to this point & membership to not be an array if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) { - $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array()); + $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, []); - $membershipLines = $nonMembershipLines = array(); + $membershipLines = $nonMembershipLines = []; foreach ($unprocessedLineItems as $priceSetID => $lines) { foreach ($lines as $line) { if (!empty($line['membership_type_id'])) { @@ -1525,8 +1519,9 @@ protected function postProcessMembership( } $i = 1; + $form->_params['createdMembershipIDs'] = []; foreach ($membershipTypeIDs as $memType) { - $membershipLineItems = array(); + $membershipLineItems = []; if ($i < count($membershipTypeIDs)) { $membershipLineItems[$priceSetID][$membershipLines[$memType]] = $unprocessedLineItems[$priceSetID][$membershipLines[$memType]]; unset($unprocessedLineItems[$priceSetID][$membershipLines[$memType]]); @@ -1536,21 +1531,15 @@ protected function postProcessMembership( } $i++; $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1); - if (!empty($membershipContribution)) { - $pendingStatus = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); - $pending = ($membershipContribution->contribution_status_id == $pendingStatus) ? TRUE : FALSE; - } - else { - $pending = $isPending; - } $contributionRecurID = isset($form->_params['contributionRecurID']) ? $form->_params['contributionRecurID'] : NULL; $membershipSource = NULL; if (!empty($form->_params['membership_source'])) { $membershipSource = $form->_params['membership_source']; } - elseif (isset($form->_values['title']) && !empty($form->_values['title'])) { - $membershipSource = ts('Online Contribution:') . ' ' . $form->_values['title']; + elseif ((isset($form->_values['title']) && !empty($form->_values['title'])) || (isset($form->_values['frontend_title']) && !empty($form->_values['frontend_title']))) { + $title = !empty($form->_values['frontend_title']) ? $form->_values['frontend_title'] : $form->_values['title']; + $membershipSource = ts('Online Contribution:') . ' ' . $title; } $isPayLater = NULL; if (isset($form->_params)) { @@ -1564,12 +1553,19 @@ protected function postProcessMembership( } } + // @todo Move this into CRM_Member_BAO_Membership::processMembership + if (!empty($membershipContribution)) { + $pending = ($membershipContribution->contribution_status_id == CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending')) ? TRUE : FALSE; + } + else { + $pending = $this->getIsPending(); + } list($membership, $renewalMode, $dates) = CRM_Member_BAO_Membership::processMembership( $contactID, $memType, $isTest, date('YmdHis'), CRM_Utils_Array::value('cms_contactID', $membershipParams), $customFieldsFormatted, $numTerms, $membershipID, $pending, - $contributionRecurID, $membershipSource, $isPayLater, $campaignId, array(), $membershipContribution, + $contributionRecurID, $membershipSource, $isPayLater, $campaignId, [], $membershipContribution, $membershipLineItems ); @@ -1580,10 +1576,22 @@ protected function postProcessMembership( } if (!empty($membershipContribution)) { - // update recurring id for membership record - CRM_Member_BAO_Membership::updateRecurMembership($membership, $membershipContribution); - // Next line is probably redundant. Checksprevent it happening twice. - CRM_Member_BAO_Membership::linkMembershipPayment($membership, $membershipContribution); + // Next line is probably redundant. Checks prevent it happening twice. + $membershipPaymentParams = [ + 'membership_id' => $membership->id, + 'membership_type_id' => $membership->membership_type_id, + 'contribution_id' => $membershipContribution->id, + ]; + civicrm_api3('MembershipPayment', 'create', $membershipPaymentParams); + } + if ($membership) { + CRM_Core_BAO_CustomValueTable::postProcess($form->_params, 'civicrm_membership', $membership->id, 'Membership'); + $form->_params['createdMembershipIDs'][] = $membership->id; + $form->_params['membershipID'] = $membership->id; + + //CRM-15232: Check if membership is created and on the basis of it use + //membership receipt template to send payment receipt + $form->_values['isMembership'] = TRUE; } } if ($form->_priceSetId && !empty($form->_useForMember) && !empty($form->_lineItem)) { @@ -1606,23 +1614,7 @@ protected function postProcessMembership( $message = $this->compileErrorMessage($errors); throw new CRM_Core_Exception($message); } - $form->_params['createdMembershipIDs'] = array(); - // CRM-7851 - Moved after processing Payment Errors - //@todo - the reasoning for this being here seems a little outdated - CRM_Core_BAO_CustomValueTable::postProcess($form->_params, 'civicrm_membership', $membership->id, 'Membership'); - $form->_params['createdMembershipIDs'][] = $membership->id; - - if ($membership) { - //presumably this is only relevant for exactly 1 membership - $form->_params['membershipID'] = $membership->id; - } - - //CRM-15232: Check if membership is created and on the basis of it use - //membership receipt template to send payment receipt - if ($membership) { - $form->_values['isMembership'] = TRUE; - } if (isset($membershipContributionID)) { $form->_values['contribution_id'] = $membershipContributionID; } @@ -1639,7 +1631,7 @@ protected function postProcessMembership( // The contribution_other_id is effectively the ID for the only contribution or the non-membership contribution. // Since we have called the membership contribution (in a 2 contribution scenario) this is out // primary-contribution compared to that - but let's face it - it's all just too hard & confusing at the moment! - $paymentParams = array_merge($form->_params, array('contributionID' => $form->_values['contribution_other_id'])); + $paymentParams = array_merge($form->_params, ['contributionID' => $form->_values['contribution_other_id']]); // CRM-19792 : set necessary fields for payment processor CRM_Core_Payment_Form::mapParams($form->_bltID, $paymentParams, $paymentParams, TRUE); @@ -1648,7 +1640,7 @@ protected function postProcessMembership( // be performed yet, so do it now. if ($isPaidMembership && !$isProcessSeparateMembershipTransaction) { $paymentActionResult = $payment->doPayment($paymentParams, 'contribute'); - $paymentResults[] = array('contribution_id' => $paymentResult['contribution']->id, 'result' => $paymentActionResult); + $paymentResults[] = ['contribution_id' => $paymentResult['contribution']->id, 'result' => $paymentActionResult]; } // Do not send an email if Recurring transaction is done via Direct Mode // Email will we sent when the IPN is received. @@ -1663,6 +1655,8 @@ protected function postProcessMembership( $emailValues = array_merge($membershipParams, $form->_values); $emailValues['membership_assign'] = 1; + $emailValues['useForMember'] = !empty($form->_useForMember); + // Finally send an email receipt for pay-later scenario (although it might sometimes be caught above!) if ($totalAmount == 0) { // This feels like a bizarre hack as the variable name doesn't seem to be directly connected to it's use in the template. @@ -1678,7 +1672,7 @@ protected function postProcessMembership( $paymentProcessorIDs = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('payment_processor', $this->_values)); $this->_paymentProcessor['id'] = $paymentProcessorIDs[0]; } - $result = array('payment_status_id' => 1, 'contribution' => $membershipContribution); + $result = ['payment_status_id' => 1, 'contribution' => $membershipContribution]; $this->completeTransaction($result, $result['contribution']->id); } // return as completeTransaction() already sends the receipt mail. @@ -1754,7 +1748,7 @@ protected function processSecondaryFinancialTransaction($contactID, &$form, $tem //so for differentiating membership contribution from //main contribution. $form->_params['separate_membership_payment'] = 1; - $contributionParams = array( + $contributionParams = [ 'contact_id' => $contactID, 'line_item' => $lineItems, 'is_test' => $isTest, @@ -1762,7 +1756,7 @@ protected function processSecondaryFinancialTransaction($contactID, &$form, $tem $form->_values)), 'contribution_page_id' => $form->_id, 'source' => CRM_Utils_Array::value('source', $tempParams, CRM_Utils_Array::value('description', $tempParams)), - ); + ]; $isMonetary = !empty($form->_values['is_monetary']); if ($isMonetary) { if (empty($paymentParams['is_pay_later'])) { @@ -1783,7 +1777,7 @@ protected function processSecondaryFinancialTransaction($contactID, &$form, $tem $isRecur ); - $result = array(); + $result = []; // We're not processing the line item here because we are processing a membership. // To ensure processing of the correct parameters, replace relevant parameters @@ -1811,7 +1805,7 @@ protected function processSecondaryFinancialTransaction($contactID, &$form, $tem $form->assign('membership_trx_id', $result['trxn_id']); } - return array($membershipContribution, $result); + return [$membershipContribution, $result]; } /** @@ -1826,8 +1820,7 @@ protected function getIsPending() { // The concept of contributeMode is deprecated. // the is_monetary concept probably should be too as it can be calculated from // the existence of 'amount' & seems fragile. - if (((isset($this->_contributeMode)) || !empty - ($this->_params['is_pay_later']) + if (((isset($this->_contributeMode)) || !empty($this->_params['is_pay_later']) ) && (($this->_values['is_monetary'] && $this->_amount > 0.0)) ) { @@ -1841,7 +1834,7 @@ protected function getIsPending() { * * Ie the membership block supports a separate transactions AND the contribution form has been configured for a * contribution - * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferPayment style) + * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferCheckout style) * * @param int $formID * @param bool $amountBlockActiveOnForm @@ -1947,7 +1940,7 @@ public static function submit($params) { $form->_fields['billing_first_name'] = 1; $form->_fields['billing_last_name'] = 1; // CRM-18854 - Set form values to allow pledge to be created for api test. - if (CRM_Utils_Array::value('pledge_block_id', $params)) { + if (!empty($params['pledge_block_id'])) { $form->_values['pledge_id'] = CRM_Utils_Array::value('pledge_id', $params, NULL); $form->_values['pledge_block_id'] = $params['pledge_block_id']; $pledgeBlock = CRM_Pledge_BAO_PledgeBlock::getPledgeBlock($params['id']); @@ -1962,13 +1955,16 @@ public static function submit($params) { $form->_values['fee'] = $priceSetFields['fields']; $form->_priceSetId = $priceSetID; $form->setFormAmountFields($priceSetID); - $capabilities = array(); + $capabilities = []; if ($form->_mode) { $capabilities[] = (ucfirst($form->_mode) . 'Mode'); } $form->_paymentProcessors = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessors($capabilities); - $form->_params['payment_processor_id'] = !empty($params['payment_processor_id']) ? $params['payment_processor_id'] : 0; - $form->_paymentProcessor = $form->_paymentProcessors[$form->_params['payment_processor_id']]; + $form->_params['payment_processor_id'] = isset($params['payment_processor_id']) ? $params['payment_processor_id'] : 0; + if ($form->_params['payment_processor_id'] !== '') { + // It can be blank with a $0 transaction - then no processor needs to be selected + $form->_paymentProcessor = $form->_paymentProcessors[$form->_params['payment_processor_id']]; + } if (!empty($params['payment_processor_id'])) { // The concept of contributeMode is deprecated as is the billing_mode concept. if ($form->_paymentProcessor['billing_mode'] == 1) { @@ -1979,10 +1975,15 @@ public static function submit($params) { } } + if (!empty($params['useForMember'])) { + $form->set('useForMember', 1); + $form->_useForMember = 1; + } $priceFields = $priceFields[$priceSetID]['fields']; - CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution'); - $form->_lineItem = array($priceSetID => $lineItems); - $membershipPriceFieldIDs = array(); + $lineItems = []; + CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution', $priceSetID); + $form->_lineItem = [$priceSetID => $lineItems]; + $membershipPriceFieldIDs = []; foreach ((array) $lineItems as $lineItem) { if (!empty($lineItem['membership_type_id'])) { $form->set('useForMember', 1); @@ -1992,6 +1993,7 @@ public static function submit($params) { } } $form->set('memberPriceFieldIDS', $membershipPriceFieldIDs); + $form->setRecurringMembershipParams(); $form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params)); } @@ -2011,11 +2013,11 @@ public static function getFormParams($id, array $params) { if (!empty($params['payment_processor_id'])) { $params['is_pay_later'] = 0; } - else { - $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array( + elseif ($params['amount'] !== 0) { + $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', [ 'id' => $id, 'return' => 'is_pay_later', - )); + ]); } } if (empty($params['price_set_id'])) { @@ -2046,8 +2048,17 @@ protected function processFormSubmission($contactID) { if (!empty($this->_ccid)) { $this->_params['contribution_id'] = $this->_ccid; } + //Set email-bltID if pre/post profile contains an email. + if ($this->_emailExists == TRUE) { + foreach ($this->_params as $key => $val) { + if (substr($key, 0, 6) == 'email-' && empty($this->_params["email-{$this->_bltID}"])) { + $this->_params["email-{$this->_bltID}"] = $this->_params[$key]; + } + } + } // add a description field at the very beginning - $this->_params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']); + $title = !empty($this->_values['frontend_title']) ? $this->_values['frontend_title'] : $this->_values['title']; + $this->_params['description'] = ts('Online Contribution') . ': ' . (!empty($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $title); $this->_params['accountingCode'] = CRM_Utils_Array::value('accountingCode', $this->_values); @@ -2078,10 +2089,10 @@ protected function processFormSubmission($contactID) { CRM_Contact_BAO_Contact::processImageParams($params); } - $fields = array('email-Primary' => 1); + $fields = ['email-Primary' => 1]; // get the add to groups - $addToGroups = array(); + $addToGroups = []; // now set the values for the billing location. foreach ($this->_fields as $name => $value) { @@ -2103,8 +2114,8 @@ protected function processFormSubmission($contactID) { // normal behavior is continued. And use that variable to // process on-behalf-of functionality. if (!empty($this->_values['onbehalf_profile_id']) && empty($this->_ccid)) { - $behalfOrganization = array(); - $orgFields = array('organization_name', 'organization_id', 'org_option'); + $behalfOrganization = []; + $orgFields = ['organization_name', 'organization_id', 'org_option']; foreach ($orgFields as $fld) { if (array_key_exists($fld, $params)) { $behalfOrganization[$fld] = $params[$fld]; @@ -2118,10 +2129,10 @@ protected function processFormSubmission($contactID) { $behalfOrganization[$fld] = $values; } elseif (!(strstr($fld, '-'))) { - if (in_array($fld, array( + if (in_array($fld, [ 'contribution_campaign_id', 'member_campaign_id', - ))) { + ])) { $fld = 'campaign_id'; } else { @@ -2182,7 +2193,7 @@ protected function processFormSubmission($contactID) { unset($dupeParams['honor']); } - $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($dupeParams, 'Individual', 'Unsupervised', array(), FALSE); + $contactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($dupeParams, 'Individual', 'Unsupervised', [], FALSE); // Fetch default greeting id's if creating a contact if (!$contactID) { @@ -2214,7 +2225,7 @@ protected function processFormSubmission($contactID) { $this->_contactID = $contactID; //get email primary first if exist - $subscriptionEmail = array('email' => CRM_Utils_Array::value('email-Primary', $params)); + $subscriptionEmail = ['email' => CRM_Utils_Array::value('email-Primary', $params)]; if (!$subscriptionEmail['email']) { $subscriptionEmail['email'] = CRM_Utils_Array::value("email-{$this->_bltID}", $params); } @@ -2231,7 +2242,7 @@ protected function processFormSubmission($contactID) { !empty($this->_params['is_for_organization']) ) ) { - $ufFields = array(); + $ufFields = []; foreach ($this->_fields['onbehalf'] as $name => $value) { $ufFields[$name] = 1; } @@ -2261,11 +2272,7 @@ protected function processFormSubmission($contactID) { $this->_useForMember = $this->get('useForMember'); // store the fact that this is a membership and membership type is selected - if ((!empty($membershipParams['selectMembership']) && - $membershipParams['selectMembership'] != 'no_thanks' - ) || - $this->_useForMember - ) { + if ($this->isMembershipSelected($membershipParams)) { if (!$this->_useForMember) { $this->assign('membership_assign', TRUE); $this->set('membershipTypeID', $this->_params['selectMembership']); @@ -2286,7 +2293,7 @@ protected function processFormSubmission($contactID) { $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values); } - CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE); + $this->_params = CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE); $this->doMembershipProcessing($contactID, $membershipParams, $premiumParams, $this->_lineItem); } else { @@ -2323,13 +2330,60 @@ protected function processFormSubmission($contactID) { $this->postProcessPremium($premiumParams, $result['contribution']); } if (!empty($result['contribution'])) { - // Not quite sure why it would be empty at this stage but tests show it can be ... at least in tests. + // It seems this line is hit when there is a zero dollar transaction & in tests, not sure when else. $this->completeTransaction($result, $result['contribution']->id); } return $result; } } + /** + * Return True/False if we have a membership selected on the contribution page + * @param array $membershipParams + * + * @return bool + */ + private function isMembershipSelected($membershipParams) { + $priceFieldIds = $this->get('memberPriceFieldIDS'); + if ((!empty($membershipParams['selectMembership']) && $membershipParams['selectMembership'] != 'no_thanks') + && empty($priceFieldIds)) { + return TRUE; + } + else { + $membershipParams = $this->getMembershipParamsFromPriceSet($membershipParams); + } + return !empty($membershipParams['selectMembership']); + } + + /** + * Extract the selected memberships from a priceSet + * + * @param array $membershipParams + * + * @return array + */ + private function getMembershipParamsFromPriceSet($membershipParams) { + $priceFieldIds = $this->get('memberPriceFieldIDS'); + if (empty($priceFieldIds)) { + return $membershipParams; + } + $membershipParams['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id'); + unset($priceFieldIds['id']); + $membershipTypeIds = []; + $membershipTypeTerms = []; + foreach ($priceFieldIds as $priceFieldId) { + $membershipTypeId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id'); + if ($membershipTypeId) { + $membershipTypeIds[] = $membershipTypeId; + $term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms') ?: 1; + $membershipTypeTerms[$membershipTypeId] = ($term > 1) ? $term : 1; + } + } + $membershipParams['selectMembership'] = $membershipTypeIds; + $membershipParams['types_terms'] = $membershipTypeTerms; + return $membershipParams; + } + /** * Membership processing section. * @@ -2359,7 +2413,7 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu $this->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id']; } - $customFieldsFormatted = $fieldTypes = array(); + $customFieldsFormatted = $fieldTypes = []; if (!empty($membershipParams['onbehalf']) && is_array($membershipParams['onbehalf']) ) { @@ -2376,39 +2430,15 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu ); } } - $fieldTypes = array('Contact', 'Organization', 'Membership'); + $fieldTypes = ['Contact', 'Organization', 'Membership']; } - $priceFieldIds = $this->get('memberPriceFieldIDS'); - - if (!empty($priceFieldIds)) { - $financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id'); - unset($priceFieldIds['id']); - $membershipTypeIds = array(); - $membershipTypeTerms = array(); - foreach ($priceFieldIds as $priceFieldId) { - if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) { - $membershipTypeIds[] = $id; - //@todo the value for $term is immediately overwritten. It is unclear from the code whether it was intentional to - // do this or a double = was intended (this ambiguity is the reason many IDEs complain about 'assignment in condition' - $term = 1; - if ($term = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_num_terms')) { - $membershipTypeTerms[$id] = ($term > 1) ? $term : 1; - } - else { - $membershipTypeTerms[$id] = 1; - } - } - } - $membershipParams['selectMembership'] = $membershipTypeIds; - $membershipParams['financial_type_id'] = $financialTypeID; - $membershipParams['types_terms'] = $membershipTypeTerms; - } + $membershipParams = $this->getMembershipParamsFromPriceSet($membershipParams); if (!empty($membershipParams['selectMembership'])) { // CRM-12233 $membershipLineItems = $formLineItems; if ($this->_separateMembershipPayment && $this->_values['amount_block_is_active']) { - $membershipLineItems = array(); + $membershipLineItems = []; foreach ($this->_values['fee'] as $key => $feeValues) { if ($feeValues['name'] == 'membership_amount') { $fieldId = $this->_params['price_' . $key]; @@ -2421,6 +2451,14 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu try { $this->processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems); } + catch (\Civi\Payment\Exception\PaymentProcessorException $e) { + CRM_Core_Session::singleton()->setStatus($e->getMessage()); + if (!empty($this->_contributionID)) { + CRM_Contribute_BAO_Contribution::failPayment($this->_contributionID, + $contactID, $e->getMessage()); + } + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}")); + } catch (CRM_Core_Exception $e) { CRM_Core_Session::singleton()->setStatus($e->getMessage()); CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}")); @@ -2452,16 +2490,16 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu protected function completeTransaction($result, $contributionID) { if (CRM_Utils_Array::value('payment_status_id', $result) == 1) { try { - civicrm_api3('contribution', 'completetransaction', array( + civicrm_api3('contribution', 'completetransaction', [ 'id' => $contributionID, 'trxn_id' => CRM_Utils_Array::value('trxn_id', $result), - 'payment_processor_id' => $this->_paymentProcessor['id'], + 'payment_processor_id' => CRM_Utils_Array::value('payment_processor_id', $result, $this->_paymentProcessor['id']), 'is_transactional' => FALSE, 'fee_amount' => CRM_Utils_Array::value('fee_amount', $result), 'receive_date' => CRM_Utils_Array::value('receive_date', $result), 'card_type_id' => CRM_Utils_Array::value('card_type_id', $result), 'pan_truncation' => CRM_Utils_Array::value('pan_truncation', $result), - )); + ]); } catch (CiviCRM_API3_Exception $e) { if ($e->getErrorCode() != 'contribution_completed') { @@ -2471,4 +2509,18 @@ protected function completeTransaction($result, $contributionID) { } } + /** + * Bounce the user back to retry when an error occurs. + * + * @param string $message + */ + protected function bounceOnError($message) { + CRM_Core_Session::singleton() + ->setStatus(ts("Payment Processor Error message :") . + $message); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', + "_qf_Main_display=true&qfKey={$this->_params['qfKey']}" + )); + } + } diff --git a/CRM/Contribute/Form/Contribution/Main.php b/CRM/Contribute/Form/Contribution/Main.php index 2187147acd2b..69fc0192cb7c 100644 --- a/CRM/Contribute/Form/Contribution/Main.php +++ b/CRM/Contribute/Form/Contribution/Main.php @@ -1,9 +1,9 @@ _paymentProcessors = $this->get('paymentProcessors'); $this->preProcessPaymentOptions(); - if (!empty($this->_ccid)) { - $payment = CRM_Contribute_BAO_Contribution::getPaymentInfo($this->_ccid, 'contribution'); - //bounce if the contribution is not pending. - if (empty($payment['balance'])) { - CRM_Core_Error::statusBounce(ts("Returning since contribution has already been handled.")); - } - if (!empty($payment['total'])) { - $this->_pendingAmount = $payment['total']; - $this->assign('pendingAmount', $this->_pendingAmount); - } - $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_ccid); - foreach (array_keys($lineItems) as $id) { - $lineItems[$id]['id'] = $id; - } - $itemId = key($lineItems); - if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) { - $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id'); - } - - if (!empty($lineItems[$itemId]['price_field_id'])) { - $this->_lineItem[$this->_priceSetId] = $lineItems; - } - $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config'); - $this->assign('lineItem', $this->_lineItem); - $this->assign('is_quick_config', $isQuickConfig); - $this->assign('priceSetID', $this->_priceSetId); - } + $this->assignFormVariablesByContributionID(); // Make the contributionPageID available to the template $this->assign('contributionPageID', $this->_id); @@ -129,8 +104,8 @@ public function setDefaultValues() { $contactID = $this->getContactID(); if (!empty($contactID)) { - $fields = array(); - $removeCustomFieldTypes = array('Contribution', 'Membership'); + $fields = []; + $removeCustomFieldTypes = ['Contribution', 'Membership']; $contribFields = CRM_Contribute_BAO_Contribution::getContributionFields(); // remove component related fields @@ -178,7 +153,7 @@ public function setDefaultValues() { //build set default for pledge overdue payment. if (!empty($this->_values['pledge_id'])) { //used to record completed pledge payment ids used later for honor default - $completedContributionIds = array(); + $completedContributionIds = []; $pledgePayments = CRM_Pledge_BAO_PledgePayment::getPledgePayments($this->_values['pledge_id']); $paymentAmount = 0; @@ -200,7 +175,7 @@ public function setDefaultValues() { $this->_defaults['price_' . $this->_priceSetId] = $paymentAmount; if (count($completedContributionIds)) { - $softCredit = array(); + $softCredit = []; foreach ($completedContributionIds as $id) { $softCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($id); } @@ -243,7 +218,7 @@ public function setDefaultValues() { $entityId = $memtypeID = NULL; if ($this->_priceSetId) { if (($this->_useForMember && !empty($this->_currentMemberships)) || $this->_defaultMemTypeId) { - $selectedCurrentMemTypes = array(); + $selectedCurrentMemTypes = []; foreach ($this->_priceSet['fields'] as $key => $val) { foreach ($val['options'] as $keys => $values) { $opMemTypeId = CRM_Utils_Array::value('membership_type_id', $values); @@ -264,14 +239,10 @@ public function setDefaultValues() { CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults); $memtypeID = $selectedCurrentMemTypes[] = $values['membership_type_id']; } - elseif (!empty($values['is_default']) && - !$opMemTypeId && - (!isset($this->_defaults[$priceFieldName]) || - ($val['html_type'] == 'CheckBox' && - !isset($this->_defaults[$priceFieldName][$keys])) - )) { - CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults); - $memtypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $this->_defaults[$priceFieldName], 'membership_type_id'); + elseif (!empty($values['is_default']) && !$opMemTypeId && (!isset($this->_defaults[$priceFieldName]) || + ($val['html_type'] == 'CheckBox' && !isset($this->_defaults[$priceFieldName][$keys])))) { + CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults); + $memtypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $this->_defaults[$priceFieldName], 'membership_type_id'); } } } @@ -328,6 +299,15 @@ public function buildQuickForm() { $this->buildComponentForm($this->_id, $this); } + if (count($this->_paymentProcessors) >= 1 && !isset($this->_paymentProcessors[0]) && !$this->get_template_vars("isCaptcha") && $this->hasToAddForcefully()) { + if (!$this->_userID) { + $this->enableCaptchaOnForm(); + } + else { + $this->displayCaptchaWarning(); + } + } + // Build payment processor form CRM_Core_Payment_ProcessorForm::buildQuickForm($this); @@ -344,24 +324,20 @@ public function buildQuickForm() { if ($this->_emailExists == FALSE) { $this->add('text', "email-{$this->_bltID}", ts('Email Address'), - array('size' => 30, 'maxlength' => 60, 'class' => 'email'), + ['size' => 30, 'maxlength' => 60, 'class' => 'email'], TRUE ); + $this->assign('showMainEmail', TRUE); $this->addRule("email-{$this->_bltID}", ts('Email is not valid.'), 'email'); } } else { $this->addElement('hidden', "email-{$this->_bltID}", 1); - $this->add('text', 'total_amount', ts('Total Amount'), array('readonly' => TRUE), FALSE); + $this->add('text', 'total_amount', ts('Total Amount'), ['readonly' => TRUE], FALSE); } - $pps = array(); - //@todo - this should be replaced by a check as to whether billing fields are set - $onlinePaymentProcessorEnabled = FALSE; + $pps = []; if (!empty($this->_paymentProcessors)) { foreach ($this->_paymentProcessors as $key => $name) { - if ($name['billing_mode'] == 1) { - $onlinePaymentProcessorEnabled = TRUE; - } $pps[$key] = $name['name']; } } @@ -386,7 +362,7 @@ public function buildQuickForm() { $contactID = $this->getContactID(); if ($this->getContactID() === 0) { - $this->addCidZeroOptions($onlinePaymentProcessorEnabled); + $this->addCidZeroOptions(); } //build pledge block. @@ -467,7 +443,7 @@ public function buildQuickForm() { $this->_values['custom_post_id'] ) { if (!is_array($this->_values['custom_post_id'])) { - $profileIDs = array($this->_values['custom_post_id']); + $profileIDs = [$this->_values['custom_post_id']]; } else { $profileIDs = $this->_values['custom_post_id']; @@ -487,7 +463,7 @@ public function buildQuickForm() { } if ($this->_pcpId && empty($this->_ccid)) { if ($pcpSupporter = CRM_PCP_BAO_PCP::displayName($this->_pcpId)) { - $pcp_supporter_text = ts('This contribution is being made thanks to the effort of %1, who supports our campaign.', array(1 => $pcpSupporter)); + $pcp_supporter_text = ts('This contribution is being made thanks to the effort of %1, who supports our campaign.', [1 => $pcpSupporter]); // Only tell people that can also create a PCP if the contribution page has a non-empty value in the "Create Personal Campaign Page link" field. $text = CRM_PCP_BAO_PCP::getPcpBlockStatus($this->_id, 'contribute'); if (!empty($text)) { @@ -495,21 +471,21 @@ public function buildQuickForm() { } $this->assign('pcpSupporterText', $pcp_supporter_text); } - $prms = array('id' => $this->_pcpId); + $prms = ['id' => $this->_pcpId]; CRM_Core_DAO::commonRetrieve('CRM_PCP_DAO_PCP', $prms, $pcpInfo); if ($pcpInfo['is_honor_roll']) { $this->assign('isHonor', TRUE); $this->add('checkbox', 'pcp_display_in_roll', ts('Show my contribution in the public honor roll'), NULL, NULL, - array('onclick' => "showHideByValue('pcp_display_in_roll','','nameID|nickID|personalNoteID','block','radio',false); pcpAnonymous( );") + ['onclick' => "showHideByValue('pcp_display_in_roll','','nameID|nickID|personalNoteID','block','radio',false); pcpAnonymous( );"] ); - $extraOption = array('onclick' => "return pcpAnonymous( );"); - $elements = array(); + $extraOption = ['onclick' => "return pcpAnonymous( );"]; + $elements = []; $elements[] = &$this->createElement('radio', NULL, '', ts('Include my name and message'), 0, $extraOption); $elements[] = &$this->createElement('radio', NULL, '', ts('List my contribution anonymously'), 1, $extraOption); $this->addGroup($elements, 'pcp_is_anonymous', NULL, '   '); - $this->add('text', 'pcp_roll_nickname', ts('Name'), array('maxlength' => 30)); - $this->add('textarea', 'pcp_personal_note', ts('Personal Note'), array('style' => 'height: 3em; width: 40em;')); + $this->add('text', 'pcp_roll_nickname', ts('Name'), ['maxlength' => 30]); + $this->addField('pcp_personal_note', ['entity' => 'ContributionSoft', 'context' => 'create', 'style' => 'height: 3em; width: 40em;']); } } if (empty($this->_values['fee']) && empty($this->_ccid)) { @@ -536,24 +512,24 @@ public function buildQuickForm() { } if (!($allAreBillingModeProcessors && !$this->_values['is_pay_later'])) { - $submitButton = array( + $submitButton = [ 'type' => 'upload', 'name' => CRM_Utils_Array::value('is_confirm_enabled', $this->_values) ? ts('Confirm Contribution') : ts('Contribute'), 'spacing' => '         ', 'isDefault' => TRUE, - ); + ]; // Add submit-once behavior when confirm page disabled if (empty($this->_values['is_confirm_enabled'])) { - $submitButton['js'] = array('onclick' => "return submitOnce(this,'" . $this->_name . "','" . ts('Processing') . "');"); + $this->submitOnce = TRUE; } //change button name for updating contribution if (!empty($this->_ccid)) { $submitButton['name'] = ts('Confirm Payment'); } - $this->addButtons(array($submitButton)); + $this->addButtons([$submitButton]); } - $this->addFormRule(array('CRM_Contribute_Form_Contribution_Main', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_Contribution_Main', 'formRule'], $this); } /** @@ -570,16 +546,16 @@ public static function buildRecur(&$form) { $form->assign('is_recur_installments', CRM_Utils_Array::value('is_recur_installments', $form->_values)); $paymentObject = $form->getVar('_paymentObject'); if ($paymentObject) { - $form->assign('recurringHelpText', $paymentObject->getText('contributionPageRecurringHelp', array( + $form->assign('recurringHelpText', $paymentObject->getText('contributionPageRecurringHelp', [ 'is_recur_installments' => !empty($form->_values['is_recur_installments']), 'is_email_receipt' => !empty($form->_values['is_email_receipt']), - ))); + ])); } $form->add('checkbox', 'is_recur', ts('I want to contribute this amount'), NULL); if (!empty($form->_values['is_recur_interval']) || $className == 'CRM_Contribute_Form_Contribution') { - $form->add('text', 'frequency_interval', ts('Every'), $attributes['frequency_interval']); + $form->add('text', 'frequency_interval', ts('Every'), $attributes['frequency_interval'] + ['aria-label' => ts('Every')]); $form->addRule('frequency_interval', ts('Frequency must be a whole number (EXAMPLE: Every 3 months).'), 'integer'); } else { @@ -610,7 +586,7 @@ public static function buildRecur(&$form) { } else { $form->assign('one_frequency_unit', FALSE); - $units = array(); + $units = []; $frequencyUnits = CRM_Core_OptionGroup::values('recur_frequency_units', FALSE, FALSE, TRUE); foreach ($unitVals as $key => $val) { if (array_key_exists($val, $frequencyUnits)) { @@ -620,7 +596,7 @@ public static function buildRecur(&$form) { } } } - $frequencyUnit = &$form->add('select', 'frequency_unit', NULL, $units); + $frequencyUnit = &$form->addElement('select', 'frequency_unit', NULL, $units, ['aria-label' => ts('Frequency Unit')]); } // FIXME: Ideally we should freeze select box if there is only @@ -642,17 +618,15 @@ public static function buildRecur(&$form) { * The input form values. * @param array $files * The uploaded files if any. - * @param CRM_Core_Form $self + * @param \CRM_Contribute_Form_Contribution_Main $self * * @return bool|array * true if no errors, else array of errors */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; $amount = self::computeAmount($fields, $self->_values); - if (CRM_Utils_Array::value('auto_renew', $fields) && - CRM_Utils_Array::value('payment_processor_id', $fields) == 0 - ) { + if (!empty($fields['auto_renew']) && empty($fields['payment_processor_id'])) { $errors['auto_renew'] = ts('You cannot have auto-renewal on if you are paying later.'); } @@ -668,7 +642,7 @@ public static function formRule($fields, $files, $self) { $membershipOrgDetails = CRM_Member_BAO_MembershipType::getMembershipTypeOrganization(); - $unallowedOrgs = array(); + $unallowedOrgs = []; foreach (array_keys($lifeMember) as $memTypeId) { $unallowedOrgs[] = $membershipOrgDetails[$memTypeId]; } @@ -681,12 +655,12 @@ public static function formRule($fields, $files, $self) { $priceField->orderBy('weight'); $priceField->find(); - $check = array(); + $check = []; $membershipIsActive = TRUE; $previousId = $otherAmount = FALSE; while ($priceField->fetch()) { - if ($self->_quickConfig && ($priceField->name == 'contribution_amount' || $priceField->name == 'membership_amount')) { + if ($self->isQuickConfig() && ($priceField->name == 'contribution_amount' || $priceField->name == 'membership_amount')) { $previousId = $priceField->id; if ($priceField->name == 'membership_amount' && !$priceField->is_active) { $membershipIsActive = FALSE; @@ -704,12 +678,12 @@ public static function formRule($fields, $files, $self) { $max = CRM_Utils_Array::value('max_amount', $self->_values); if ($min && $otherAmountVal < $min) { $errors["price_{$priceField->id}"] = ts('Contribution amount must be at least %1', - array(1 => $min) + [1 => $min] ); } if ($max && $otherAmountVal > $max) { $errors["price_{$priceField->id}"] = ts('Contribution amount cannot be more than %1.', - array(1 => $max) + [1 => $max] ); } } @@ -729,20 +703,19 @@ public static function formRule($fields, $files, $self) { // For anonymous user check using dedupe rule // if user has Cancelled Membership if (!$memContactID) { - $memContactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', array(), FALSE); + $memContactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', [], FALSE); } $currentMemberships = CRM_Member_BAO_Membership::getContactsCancelledMembership($memContactID, $is_test ); - $errorText = 'Your %1 membership was previously cancelled and can not be renewed online. Please contact the site administrator for assistance.'; foreach ($self->_values['fee'] as $fieldKey => $fieldValue) { if ($fieldValue['html_type'] != 'Text' && CRM_Utils_Array::value('price_' . $fieldKey, $fields)) { if (!is_array($fields['price_' . $fieldKey]) && isset($fieldValue['options'][$fields['price_' . $fieldKey]])) { if (array_key_exists('membership_type_id', $fieldValue['options'][$fields['price_' . $fieldKey]]) && in_array($fieldValue['options'][$fields['price_' . $fieldKey]]['membership_type_id'], $currentMemberships) ) { - $errors['price_' . $fieldKey] = ts($errorText, array(1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$fields['price_' . $fieldKey]]['membership_type_id']))); + $errors['price_' . $fieldKey] = ts('Your %1 membership was previously cancelled and can not be renewed online. Please contact the site administrator for assistance.', [1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$fields['price_' . $fieldKey]]['membership_type_id'])]); } } else { @@ -751,7 +724,7 @@ public static function formRule($fields, $files, $self) { if (array_key_exists('membership_type_id', $fieldValue['options'][$key]) && in_array($fieldValue['options'][$key]['membership_type_id'], $currentMemberships) ) { - $errors['price_' . $fieldKey] = ts($errorText, array(1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$key]['membership_type_id']))); + $errors['price_' . $fieldKey] = ts('Your %1 membership was previously cancelled and can not be renewed online. Please contact the site administrator for assistance.', [1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$key]['membership_type_id'])]); } } } @@ -801,8 +774,8 @@ public static function formRule($fields, $files, $self) { } if ($self->_useForMember == 1 && !empty($check) && $membershipIsActive) { - $priceFieldIDS = array(); - $priceFieldMemTypes = array(); + $priceFieldIDS = []; + $priceFieldMemTypes = []; foreach ($self->_priceSet['fields'] as $priceId => $value) { if (!empty($fields['price_' . $priceId]) || ($self->_quickConfig && $value['name'] == 'membership_amount' && empty($self->_membershipBlock['is_required']))) { @@ -871,9 +844,9 @@ public static function formRule($fields, $files, $self) { $errors['_qf_default'] = ts('Contribution can not be less than zero. Please select the options accordingly'); } elseif (!empty($minAmt) && $fields['amount'] < $minAmt) { - $errors['_qf_default'] = ts('A minimum amount of %1 should be selected from Contribution(s).', array( + $errors['_qf_default'] = ts('A minimum amount of %1 should be selected from Contribution(s).', [ 1 => CRM_Utils_Money::format($minAmt), - )); + ]); } $amount = $fields['amount']; @@ -888,7 +861,7 @@ public static function formRule($fields, $files, $self) { $min_amount = $productDAO->min_contribution; if ($amount < $min_amount) { - $errors['selectProduct'] = ts('The premium you have selected requires a minimum contribution of %1', array(1 => CRM_Utils_Money::format($min_amount))); + $errors['selectProduct'] = ts('The premium you have selected requires a minimum contribution of %1', [1 => CRM_Utils_Money::format($min_amount)]); CRM_Core_Session::setStatus($errors['selectProduct']); } } @@ -896,15 +869,13 @@ public static function formRule($fields, $files, $self) { //CRM-16285 - Function to handle validation errors on form, for recurring contribution field. CRM_Contribute_BAO_ContributionRecur::validateRecurContribution($fields, $files, $self, $errors); - if (!empty($fields['is_recur']) && - CRM_Utils_Array::value('payment_processor_id', $fields) == 0 - ) { + if (!empty($fields['is_recur']) && empty($fields['payment_processor_id'])) { $errors['_qf_default'] = ts('You cannot set up a recurring contribution if you are not paying online by credit card.'); } // validate PCP fields - if not anonymous, we need a nick name value if ($self->_pcpId && !empty($fields['pcp_display_in_roll']) && - (CRM_Utils_Array::value('pcp_is_anonymous', $fields) == 0) && + empty($fields['pcp_is_anonymous']) && CRM_Utils_Array::value('pcp_roll_nickname', $fields) == '' ) { $errors['pcp_roll_nickname'] = ts('Please enter a name to include in the Honor Roll, or select \'contribute anonymously\'.'); @@ -913,7 +884,7 @@ public static function formRule($fields, $files, $self) { // return if this is express mode $config = CRM_Core_Config::singleton(); if ($self->_paymentProcessor && - $self->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON + (int) $self->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON ) { if (!empty($fields[$self->_expressButtonName . '_x']) || !empty($fields[$self->_expressButtonName . '_y']) || CRM_Utils_Array::value($self->_expressButtonName, $fields) @@ -935,13 +906,13 @@ public static function formRule($fields, $files, $self) { $errors['pledge_installments'] = ts('Please enter a valid number of pledge installments.'); } else { - if (CRM_Utils_Array::value('pledge_installments', $fields) == NULL) { + if (!isset($fields['pledge_installments'])) { $errors['pledge_installments'] = ts('Pledge Installments is required field.'); } elseif (CRM_Utils_Array::value('pledge_installments', $fields) == 1) { $errors['pledge_installments'] = ts('Pledges consist of multiple scheduled payments. Select one-time contribution if you want to make your gift in a single payment.'); } - elseif (CRM_Utils_Array::value('pledge_installments', $fields) == 0) { + elseif (empty($fields['pledge_installments'])) { $errors['pledge_installments'] = ts('Pledge Installments field must be > 1.'); } } @@ -951,10 +922,10 @@ public static function formRule($fields, $files, $self) { $errors['pledge_frequency_interval'] = ts('Please enter a valid Pledge Frequency Interval.'); } else { - if (CRM_Utils_Array::value('pledge_frequency_interval', $fields) == NULL) { + if (!isset($fields['pledge_frequency_interval'])) { $errors['pledge_frequency_interval'] = ts('Pledge Frequency Interval. is required field.'); } - elseif (CRM_Utils_Array::value('pledge_frequency_interval', $fields) == 0) { + elseif (empty($fields['pledge_frequency_interval'])) { $errors['pledge_frequency_interval'] = ts('Pledge frequency interval field must be > 0'); } } @@ -967,7 +938,7 @@ public static function formRule($fields, $files, $self) { return $errors; } - if (CRM_Utils_Array::value('payment_processor_id', $fields) == NULL) { + if (!isset($fields['payment_processor_id'])) { $errors['payment_processor_id'] = ts('Payment Method is a required field.'); } else { @@ -981,10 +952,10 @@ public static function formRule($fields, $files, $self) { foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) { if ($greetingType = CRM_Utils_Array::value($greeting, $fields)) { - $customizedValue = CRM_Core_OptionGroup::getValue($greeting, 'Customized', 'name'); + $customizedValue = CRM_Core_PseudoConstant::getKey('CRM_Contact_BAO_Contact', $greeting . '_id', 'Customized'); if ($customizedValue == $greetingType && empty($fielse[$greeting . '_custom'])) { $errors[$greeting . '_custom'] = ts('Custom %1 is a required field if %1 is of type Customized.', - array(1 => ucwords(str_replace('_', " ", $greeting))) + [1 => ucwords(str_replace('_', " ", $greeting))] ); } } @@ -1075,7 +1046,7 @@ public function submit($params) { $priceField->orderBy('weight'); $priceField->find(); - $priceOptions = array(); + $priceOptions = []; while ($priceField->fetch()) { CRM_Price_BAO_PriceFieldValue::getValues($priceField->id, $priceOptions); if (($selectedPriceOptionID = CRM_Utils_Array::value("price_{$priceField->id}", $params)) != FALSE && $selectedPriceOptionID > 0) { @@ -1167,7 +1138,7 @@ public function submit($params) { $this->set('lineItem', $this->_lineItem); } elseif ($priceSetId = CRM_Utils_Array::value('priceSetId', $params)) { - $lineItem = array(); + $lineItem = []; $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config'); if ($is_quick_config) { foreach ($this->_values['fee'] as $key => & $val) { @@ -1190,7 +1161,7 @@ public function submit($params) { $component = 'membership'; } - CRM_Price_BAO_PriceSet::processAmount($this->_values['fee'], $params, $lineItem[$priceSetId], $component); + CRM_Price_BAO_PriceSet::processAmount($this->_values['fee'], $params, $lineItem[$priceSetId], $component, $priceSetId); if ($params['tax_amount']) { $this->set('tax_amount', $params['tax_amount']); } @@ -1212,7 +1183,7 @@ public function submit($params) { if ($params['amount'] != 0 && (($this->_values['is_pay_later'] && empty($this->_paymentProcessor) && !array_key_exists('hidden_processor', $params)) || - (CRM_Utils_Array::value('payment_processor_id', $params) == 0)) + empty($params['payment_processor_id'])) ) { $params['is_pay_later'] = 1; } @@ -1241,7 +1212,8 @@ public function submit($params) { $invoiceID = md5(uniqid(rand(), TRUE)); $this->set('invoiceID', $invoiceID); $params['invoiceID'] = $invoiceID; - $params['description'] = ts('Online Contribution') . ': ' . ((!empty($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title'])); + $title = !empty($this->_values['frontend_title']) ? $this->_values['frontend_title'] : $this->_values['title']; + $params['description'] = ts('Online Contribution') . ': ' . ((!empty($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $title)); $params['button'] = $this->controller->getButtonName(); // required only if is_monetary and valid positive amount // @todo it seems impossible for $memFee to be greater than 0 & $params['amount'] not to @@ -1318,6 +1290,50 @@ protected function skipToThankYouPage() { CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE)); } + /** + * Set form variables if contribution ID is found + */ + public function assignFormVariablesByContributionID() { + if (empty($this->_ccid)) { + return; + } + if (!$this->getContactID()) { + CRM_Core_Error::statusBounce(ts("Returning since there is no contact attached to this contribution id.")); + } + + $paymentBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($this->_ccid); + //bounce if the contribution is not pending. + if ((int) $paymentBalance <= 0) { + CRM_Core_Error::statusBounce(ts("Returning since contribution has already been handled.")); + } + if (!empty($paymentBalance)) { + $this->_pendingAmount = $paymentBalance; + $this->assign('pendingAmount', $this->_pendingAmount); + } + + if ($taxAmount = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->_ccid, 'tax_amount')) { + $this->set('tax_amount', $taxAmount); + $this->assign('taxAmount', $taxAmount); + } + + $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_ccid); + foreach (array_keys($lineItems) as $id) { + $lineItems[$id]['id'] = $id; + } + $itemId = key($lineItems); + if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) { + $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id'); + } + + if (!empty($lineItems[$itemId]['price_field_id'])) { + $this->_lineItem[$this->_priceSetId] = $lineItems; + } + $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config'); + $this->assign('lineItem', $this->_lineItem); + $this->assign('is_quick_config', $isQuickConfig); + $this->assign('priceSetID', $this->_priceSetId); + } + /** * Function for unit tests on the postProcess function. * diff --git a/CRM/Contribute/Form/Contribution/ThankYou.php b/CRM/Contribute/Form/Contribution/ThankYou.php index 62dee7c0509b..842feec3b5a7 100644 --- a/CRM/Contribute/Form/Contribution/ThankYou.php +++ b/CRM/Contribute/Form/Contribution/ThankYou.php @@ -1,9 +1,9 @@ _params = $this->get('params'); $this->_lineItem = $this->get('lineItem'); + $this->_useForMember = $this->get('useForMember'); $is_deductible = $this->get('is_deductible'); $this->assign('is_deductible', $is_deductible); $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $this->_values)); @@ -88,6 +96,7 @@ public function getAction() { * Build the form object. */ public function buildQuickForm() { + // FIXME: Some of this code is identical to Confirm.php and should be broken out into a shared function $this->assignToTemplate(); $this->_ccid = $this->get('ccid'); $productID = $this->get('productID'); @@ -98,45 +107,49 @@ public function buildQuickForm() { if ($productID) { CRM_Contribute_BAO_Premium::buildPremiumBlock($this, $this->_id, FALSE, $productID, $option); } - if ($this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { - $this->assign('lineItem', $this->_lineItem); - } - else { - if (is_array($membershipTypeID)) { - $membershipTypeID = current($membershipTypeID); - } - $this->assign('is_quick_config', 1); - $this->_params['is_quick_config'] = 1; - } - $this->assign('priceSetID', $this->_priceSetId); - $this->assign('useForMember', $this->get('useForMember')); $params = $this->_params; - $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); + $invoicing = CRM_Invoicing_Utils::isInvoicingEnabled(); + // Make a copy of line items array to use for display only + $tplLineItems = $this->_lineItem; if ($invoicing) { $getTaxDetails = FALSE; - $taxTerm = CRM_Utils_Array::value('tax_term', $invoiceSettings); - foreach ($this->_lineItem as $value) { - foreach ($value as $v) { + foreach ($this->_lineItem as $key => $value) { + foreach ($value as $k => $v) { if (isset($v['tax_rate'])) { if ($v['tax_rate'] != '') { $getTaxDetails = TRUE; + // Cast to float to display without trailing zero decimals + $tplLineItems[$key][$k]['tax_rate'] = (float) $v['tax_rate']; } } } } $this->assign('getTaxDetails', $getTaxDetails); - $this->assign('taxTerm', $taxTerm); + $this->assign('taxTerm', CRM_Invoicing_Utils::getTaxTerm()); $this->assign('totalTaxAmount', $params['tax_amount']); } + + if ($this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { + $this->assign('lineItem', $tplLineItems); + } + else { + if (is_array($membershipTypeID)) { + $membershipTypeID = current($membershipTypeID); + } + $this->assign('is_quick_config', 1); + $this->_params['is_quick_config'] = 1; + } + $this->assign('priceSetID', $this->_priceSetId); + $this->assign('useForMember', $this->get('useForMember')); + if (!empty($this->_values['honoree_profile_id']) && !empty($params['soft_credit_type_id'])) { $softCreditTypes = CRM_Core_OptionGroup::values("soft_credit_type", FALSE); $this->assign('soft_credit_type', $softCreditTypes[$params['soft_credit_type_id']]); CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields($this, $params['honor']); - $fieldTypes = array('Contact'); + $fieldTypes = ['Contact']; $fieldTypes[] = CRM_Core_BAO_UFGroup::getContactType($this->_values['honoree_profile_id']); $this->buildCustom($this->_values['honoree_profile_id'], 'honoreeProfileFields', TRUE, 'honor', $fieldTypes); } @@ -146,12 +159,12 @@ public function buildQuickForm() { if ($this->_pcpId) { $qParams .= "&pcpId={$this->_pcpId}"; $this->assign('pcpBlock', TRUE); - foreach (array( - 'pcp_display_in_roll', - 'pcp_is_anonymous', - 'pcp_roll_nickname', - 'pcp_personal_note', - ) as $val) { + foreach ([ + 'pcp_display_in_roll', + 'pcp_is_anonymous', + 'pcp_roll_nickname', + 'pcp_personal_note', + ] as $val) { if (!empty($this->_params[$val])) { $this->assign($val, $this->_params[$val]); } @@ -194,30 +207,29 @@ public function buildQuickForm() { !empty($params['is_for_organization']) ) && empty($this->_ccid) ) { - $fieldTypes = array('Contact', 'Organization'); + $fieldTypes = ['Contact', 'Organization']; $contactSubType = CRM_Contact_BAO_ContactType::subTypes('Organization'); $fieldTypes = array_merge($fieldTypes, $contactSubType); if (is_array($this->_membershipBlock) && !empty($this->_membershipBlock)) { - $fieldTypes = array_merge($fieldTypes, array('Membership')); + $fieldTypes = array_merge($fieldTypes, ['Membership']); } else { - $fieldTypes = array_merge($fieldTypes, array('Contribution')); + $fieldTypes = array_merge($fieldTypes, ['Contribution']); } $this->buildCustom($this->_values['onbehalf_profile_id'], 'onbehalfProfile', TRUE, 'onbehalf', $fieldTypes); } - $this->assign('trxn_id', - CRM_Utils_Array::value('trxn_id', - $this->_params - ) - ); + $this->_trxnId = CRM_Utils_Array::value('trxn_id', $this->_params); + + $this->assign('trxn_id', $this->_trxnId); + $this->assign('receive_date', CRM_Utils_Date::mysqlToIso(CRM_Utils_Array::value('receive_date', $this->_params)) ); - $defaults = array(); - $fields = array(); + $defaults = []; + $fields = []; foreach ($this->_fields as $name => $dontCare) { if ($name != 'onbehalf' || $name != 'honor') { $fields[$name] = 1; @@ -235,11 +247,11 @@ public function buildQuickForm() { $defaults[$timeField] = $contact[$timeField]; } } - elseif (in_array($name, array( - 'addressee', - 'email_greeting', - 'postal_greeting', - )) && !empty($contact[$name . '_custom']) + elseif (in_array($name, [ + 'addressee', + 'email_greeting', + 'postal_greeting', + ]) && !empty($contact[$name . '_custom']) ) { $defaults[$name . '_custom'] = $contact[$name . '_custom']; } @@ -286,11 +298,12 @@ public function buildQuickForm() { $isPendingOutcome = TRUE; try { // A payment notification update could have come in at any time. Check at the last minute. - $contributionStatusID = civicrm_api3('Contribution', 'getvalue', array( + $contributionStatusID = civicrm_api3('Contribution', 'getvalue', [ 'id' => CRM_Utils_Array::value('contributionID', $params), 'return' => 'contribution_status_id', + 'is_test' => ($this->_mode == 'test') ? 1 : 0, 'invoice_id' => CRM_Utils_Array::value('invoiceID', $params), - )); + ]); if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contributionStatusID) === 'Pending' && !empty($params['payment_processor_id']) ) { diff --git a/CRM/Contribute/Form/ContributionBase.php b/CRM/Contribute/Form/ContributionBase.php index 72bd332241c6..21cc18e75a1c 100644 --- a/CRM/Contribute/Form/ContributionBase.php +++ b/CRM/Contribute/Form/ContributionBase.php @@ -1,9 +1,9 @@ controller->invalidKeyRedirect(); } + $this->_emailExists = $this->get('emailExists'); // this was used prior to the cleverer this_>getContactID - unsure now - $this->_userID = CRM_Core_Session::singleton()->get('userID'); + $this->_userID = CRM_Core_Session::singleton()->getLoggedInContactID(); $this->_contactID = $this->_membershipContactID = $this->getContactID(); $this->_mid = NULL; @@ -278,9 +286,6 @@ public function preProcess() { // we do not want to display recently viewed items, so turn off $this->assign('displayRecent', FALSE); - // Contribution page values are cleared from session, so can't use normal Printer Friendly view. - // Use Browser Print instead. - $this->assign('browserPrint', TRUE); // action $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add'); @@ -293,13 +298,14 @@ public function preProcess() { $this->_fields = $this->get('fields'); $this->_bltID = $this->get('bltID'); $this->_paymentProcessor = $this->get('paymentProcessor'); + $this->_priceSetId = $this->get('priceSetId'); $this->_priceSet = $this->get('priceSet'); if (!$this->_values) { // get all the values from the dao object - $this->_values = array(); - $this->_fields = array(); + $this->_values = []; + $this->_fields = []; CRM_Contribute_BAO_ContributionPage::setValues($this->_id, $this->_values); if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() @@ -311,6 +317,17 @@ public function preProcess() { throw new CRM_Contribute_Exception_InactiveContributionPageException(ts('The page you requested is currently unavailable.'), $this->_id); } + $endDate = CRM_Utils_Date::processDate(CRM_Utils_Array::value('end_date', $this->_values)); + $now = date('YmdHis'); + if ($endDate && $endDate < $now) { + throw new CRM_Contribute_Exception_PastContributionPageException(ts('The page you requested has past its end date on %1', [1 => CRM_Utils_Date::customFormat($endDate)]), $this->_id); + } + + $startDate = CRM_Utils_Date::processDate(CRM_Utils_Array::value('start_date', $this->_values)); + if ($startDate && $startDate > $now) { + throw new CRM_Contribute_Exception_FutureContributionPageException(ts('The page you requested will be active from %1', [1 => CRM_Utils_Date::customFormat($startDate)]), $this->_id); + } + $this->assignBillingType(); // check for is_monetary status @@ -327,13 +344,11 @@ public function preProcess() { } } - if ($isMonetary && - (!$isPayLater || !empty($this->_values['payment_processor'])) - ) { - $this->_paymentProcessorIDs = explode( + if ($isMonetary) { + $this->_paymentProcessorIDs = array_filter(explode( CRM_Core_DAO::VALUE_SEPARATOR, CRM_Utils_Array::value('payment_processor', $this->_values) - ); + )); $this->assignPaymentProcessor($isPayLater); } @@ -343,11 +358,11 @@ public function preProcess() { CRM_Price_BAO_PriceSet::initSet($this, $this->_id, 'civicrm_contribution_page'); // this avoids getting E_NOTICE errors in php - $setNullFields = array( + $setNullFields = [ 'amount_block_is_active', 'is_allow_other_amount', 'footer_text', - ); + ]; foreach ($setNullFields as $f) { if (!isset($this->_values[$f])) { $this->_values[$f] = NULL; @@ -453,25 +468,17 @@ public function preProcess() { CRM_Utils_Array::value('cancelSubscriptionUrl', $this->_values) ); - // assigning title to template in case someone wants to use it, also setting CMS page title - if ($this->_pcpId) { - $this->assign('title', $this->_pcpInfo['title']); - CRM_Utils_System::setTitle($this->_pcpInfo['title']); - } - else { - $this->assign('title', $this->_values['title']); - CRM_Utils_System::setTitle($this->_values['title']); - } - $this->_defaults = array(); + $title = !empty($this->_values['frontend_title']) ? $this->_values['frontend_title'] : $this->_values['title']; + + $this->setTitle(($this->_pcpId ? $this->_pcpInfo['title'] : $title)); + $this->_defaults = []; $this->_amount = $this->get('amount'); + // Assigning this to the template means it will be passed through to the payment form. + // This can, for example, by used by payment processors using client side encryption + $this->assign('currency', $this->getCurrency()); - //CRM-6907 - $config = CRM_Core_Config::singleton(); - $config->defaultCurrency = CRM_Utils_Array::value('currency', - $this->_values, - $config->defaultCurrency - ); + CRM_Contribute_BAO_Contribution_Utils::overrideDefaultCurrency($this->_values); //lets allow user to override campaign. $campID = CRM_Utils_Request::retrieve('campID', 'Positive', $this); @@ -505,23 +512,23 @@ public function assignToTemplate() { $this->set('name', $this->assignBillingName($this->_params)); $this->assign('paymentProcessor', $this->_paymentProcessor); - $vars = array( + $vars = [ 'amount', 'currencyID', 'credit_card_type', 'trxn_id', 'amount_level', - ); + ]; $config = CRM_Core_Config::singleton(); if (isset($this->_values['is_recur']) && !empty($this->_paymentProcessor['is_recur'])) { $this->assign('is_recur_enabled', 1); - $vars = array_merge($vars, array( + $vars = array_merge($vars, [ 'is_recur', 'frequency_interval', 'frequency_unit', 'installments', - )); + ]); } if (in_array('CiviPledge', $config->enableComponents) && @@ -529,12 +536,12 @@ public function assignToTemplate() { ) { $this->assign('pledge_enabled', 1); - $vars = array_merge($vars, array( + $vars = array_merge($vars, [ 'is_pledge', 'pledge_frequency_interval', 'pledge_frequency_unit', 'pledge_installments', - )); + ]); } // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel @@ -564,39 +571,7 @@ public function assignToTemplate() { $locTypeId = array_keys($this->_params['onbehalf_location']['email']); $this->assign('onBehalfEmail', $this->_params['onbehalf_location']['email'][$locTypeId[0]]['email']); } - - //fix for CRM-3767 - $assignCCInfo = FALSE; - if ($this->_amount > 0.0) { - $assignCCInfo = TRUE; - } - elseif (!empty($this->_params['selectMembership'])) { - $memFee = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $this->_params['selectMembership'], 'minimum_fee'); - if ($memFee > 0.0) { - $assignCCInfo = TRUE; - } - } - - // The concept of contributeMode is deprecated. - // The payment processor object can provide info about the fields it shows. - if ($this->_contributeMode == 'direct' && $assignCCInfo) { - if ($this->_paymentProcessor && - $this->_paymentProcessor['payment_type'] & CRM_Core_Payment::PAYMENT_TYPE_DIRECT_DEBIT - ) { - $this->assign('account_holder', $this->_params['account_holder']); - $this->assign('bank_identification_number', $this->_params['bank_identification_number']); - $this->assign('bank_name', $this->_params['bank_name']); - $this->assign('bank_account_number', $this->_params['bank_account_number']); - } - else { - $date = CRM_Utils_Date::format(CRM_Utils_Array::value('credit_card_exp_date', $this->_params)); - $date = CRM_Utils_Date::mysqlToIso($date); - $this->assign('credit_card_exp_date', $date); - $this->assign('credit_card_number', - CRM_Utils_System::mungeCreditCard(CRM_Utils_Array::value('credit_card_number', $this->_params)) - ); - } - } + $this->assignPaymentFields(); $this->assign('email', $this->controller->exportValue('Main', "email-{$this->_bltID}") @@ -623,7 +598,7 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = // we don't allow conflicting fields to be // configured via profile - CRM 2100 - $fieldsToIgnore = array( + $fieldsToIgnore = [ 'receive_date' => 1, 'trxn_id' => 1, 'invoice_id' => 1, @@ -639,7 +614,7 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = 'payment_instrument_id' => 1, 'contribution_check_number' => 1, 'financial_type' => 1, - ); + ]; $fields = CRM_Core_BAO_UFGroup::getFields($id, FALSE, CRM_Core_Action::ADD, NULL, NULL, FALSE, NULL, FALSE, NULL, CRM_Core_Permission::CREATE, NULL @@ -649,9 +624,10 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = // determine if email exists in profile so we know if we need to manually insert CRM-2888, CRM-15067 foreach ($fields as $key => $field) { if (substr($key, 0, 6) == 'email-' && - !in_array($profileContactType, array('honor', 'onbehalf')) + !in_array($profileContactType, ['honor', 'onbehalf']) ) { $this->_emailExists = TRUE; + $this->set('emailExists', TRUE); } } @@ -661,14 +637,14 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = } //remove common fields only if profile is not configured for onbehalf/honor - if (!in_array($profileContactType, array('honor', 'onbehalf'))) { + if (!in_array($profileContactType, ['honor', 'onbehalf'])) { $fields = array_diff_key($fields, $this->_fields); } CRM_Core_BAO_Address::checkContactSharedAddressFields($fields, $contactID); $addCaptcha = FALSE; // fetch file preview when not submitted yet, like in online contribution Confirm and ThankYou page - $viewOnlyFileValues = empty($profileContactType) ? array() : array($profileContactType => array()); + $viewOnlyFileValues = empty($profileContactType) ? [] : [$profileContactType => []]; foreach ($fields as $key => $field) { if ($viewOnly && isset($field['data_type']) && @@ -701,14 +677,14 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = if ($profileContactType) { //Since we are showing honoree name separately so we are removing it from honoree profile just for display if ($profileContactType == 'honor') { - $honoreeNamefields = array( + $honoreeNamefields = [ 'prefix_id', 'first_name', 'last_name', 'suffix_id', 'organization_name', 'household_name', - ); + ]; if (in_array($field['name'], $honoreeNamefields)) { unset($fields[$field['name']]); continue; @@ -755,14 +731,89 @@ public function buildCustom($id, $name, $viewOnly = FALSE, $profileContactType = } if ($addCaptcha && !$viewOnly) { - $captcha = CRM_Utils_ReCAPTCHA::singleton(); - $captcha->add($this); - $this->assign('isCaptcha', TRUE); + $this->enableCaptchaOnForm(); + } + } + } + } + + /** + * Enable ReCAPTCHA on Contribution form + */ + protected function enableCaptchaOnForm() { + CRM_Utils_ReCAPTCHA::enableCaptchaOnForm($this); + } + + public function assignPaymentFields() { + //fix for CRM-3767 + $isMonetary = FALSE; + if ($this->_amount > 0.0) { + $isMonetary = TRUE; + } + elseif (!empty($this->_params['selectMembership'])) { + $memFee = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $this->_params['selectMembership'], 'minimum_fee'); + if ($memFee > 0.0) { + $isMonetary = TRUE; + } + } + + // The concept of contributeMode is deprecated. + // The payment processor object can provide info about the fields it shows. + if ($isMonetary && is_a($this->_paymentProcessor['object'], 'CRM_Core_Payment')) { + /** @var $paymentProcessorObject \CRM_Core_Payment */ + $paymentProcessorObject = $this->_paymentProcessor['object']; + + $paymentFields = $paymentProcessorObject->getPaymentFormFields(); + foreach ($paymentFields as $index => $paymentField) { + if (!isset($this->_params[$paymentField])) { + unset($paymentFields[$index]); + continue; + } + if ($paymentField === 'credit_card_exp_date') { + $date = CRM_Utils_Date::format(CRM_Utils_Array::value('credit_card_exp_date', $this->_params)); + $date = CRM_Utils_Date::mysqlToIso($date); + $this->assign('credit_card_exp_date', $date); + } + elseif ($paymentField === 'credit_card_number') { + $this->assign('credit_card_number', + CRM_Utils_System::mungeCreditCard(CRM_Utils_Array::value('credit_card_number', $this->_params)) + ); + } + elseif ($paymentField === 'credit_card_type') { + $this->assign('credit_card_type', CRM_Core_PseudoConstant::getLabel( + 'CRM_Core_BAO_FinancialTrxn', + 'card_type_id', + CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'card_type_id', $this->_params['credit_card_type']) + )); + } + else { + $this->assign($paymentField, $this->_params[$paymentField]); } } + $this->assign('paymentFieldsetLabel', CRM_Core_Payment_Form::getPaymentLabel($paymentProcessorObject)); + $this->assign('paymentFields', $paymentFields); + } } + /** + * Display ReCAPTCHA warning on Contribution form + */ + protected function displayCaptchaWarning() { + if (CRM_Core_Permission::check("administer CiviCRM")) { + if (!CRM_Utils_ReCAPTCHA::hasSettingsAvailable()) { + $this->assign('displayCaptchaWarning', TRUE); + } + } + } + + /** + * Check if ReCAPTCHA has to be added on Contribution form forcefully. + */ + protected function hasToAddForcefully() { + return CRM_Utils_ReCAPTCHA::hasToAddForcefully(); + } + /** * Add onbehalf/honoree profile fields and native module fields. * @@ -776,7 +827,7 @@ public function buildComponentForm($id, $form) { $contactID = $this->getContactID(); - foreach (array('soft_credit', 'on_behalf') as $module) { + foreach (['soft_credit', 'on_behalf'] as $module) { if ($module == 'soft_credit') { if (empty($form->_values['honoree_profile_id'])) { continue; @@ -787,17 +838,17 @@ public function buildComponentForm($id, $form) { } $profileContactType = CRM_Core_BAO_UFGroup::getContactType($form->_values['honoree_profile_id']); - $requiredProfileFields = array( - 'Individual' => array('first_name', 'last_name'), - 'Organization' => array('organization_name', 'email'), - 'Household' => array('household_name', 'email'), - ); + $requiredProfileFields = [ + 'Individual' => ['first_name', 'last_name'], + 'Organization' => ['organization_name', 'email'], + 'Household' => ['household_name', 'email'], + ]; $validProfile = CRM_Core_BAO_UFGroup::checkValidProfile($form->_values['honoree_profile_id'], $requiredProfileFields[$profileContactType]); if (!$validProfile) { CRM_Core_Error::fatal(ts('This contribution page has been configured for contribution on behalf of honoree and the required fields of the selected honoree profile are disabled or doesn\'t exist.')); } - foreach (array('honor_block_title', 'honor_block_text') as $name) { + foreach (['honor_block_title', 'honor_block_text'] as $name) { $form->assign($name, $form->_values[$name]); } @@ -844,11 +895,11 @@ public function buildComponentForm($id, $form) { $msg = ts('Mixed profile not allowed for on behalf of registration/sign up.'); $onBehalfProfile = CRM_Core_BAO_UFGroup::profileGroups($form->_values['onbehalf_profile_id']); foreach ( - array( + [ 'Individual', 'Organization', 'Household', - ) as $contactType + ] as $contactType ) { if (in_array($contactType, $onBehalfProfile) && (in_array('Membership', $onBehalfProfile) || @@ -866,17 +917,17 @@ public function buildComponentForm($id, $form) { if (count($organizations)) { // Related org url - pass checksum if needed - $args = array( + $args = [ 'ufId' => $form->_values['onbehalf_profile_id'], 'cid' => '', - ); + ]; if (!empty($_GET['cs'])) { - $args = array( + $args = [ 'ufId' => $form->_values['onbehalf_profile_id'], 'uid' => $this->_contactID, 'cs' => $_GET['cs'], 'cid' => '', - ); + ]; } $locDataURL = CRM_Utils_System::url('civicrm/ajax/permlocation', $args, FALSE, NULL, FALSE); $form->assign('locDataURL', $locDataURL); @@ -884,16 +935,16 @@ public function buildComponentForm($id, $form) { if (count($organizations) > 0) { $form->add('select', 'onbehalfof_id', '', CRM_Utils_Array::collect('name', $organizations)); - $orgOptions = array( + $orgOptions = [ 0 => ts('Select an existing organization'), 1 => ts('Enter a new organization'), - ); + ]; $form->addRadio('org_option', ts('options'), $orgOptions); - $form->setDefaults(array('org_option' => 0)); + $form->setDefaults(['org_option' => 0]); } } - $form->assign('fieldSetTitle', ts('Organization Details')); + $form->assign('fieldSetTitle', CRM_Core_BAO_UFGroup::getTitle($form->_values['onbehalf_profile_id'])); if (CRM_Utils_Array::value('is_for_organization', $form->_values)) { if ($form->_values['is_for_organization'] == 2) { @@ -919,17 +970,20 @@ public function buildComponentForm($id, $form) { if (!empty($form->_submitValues['onbehalfof_id'])) { $form->assign('submittedOnBehalf', $form->_submitValues['onbehalfof_id']); } - $form->assign('submittedOnBehalfInfo', json_encode($form->_submitValues['onbehalf'])); + $form->assign('submittedOnBehalfInfo', json_encode(str_replace('"', '\"', $form->_submitValues['onbehalf']), JSON_HEX_APOS)); } - $fieldTypes = array('Contact', 'Organization'); + $fieldTypes = ['Contact', 'Organization']; + if (!empty($form->_membershipBlock)) { + $fieldTypes = array_merge($fieldTypes, ['Membership']); + } $contactSubType = CRM_Contact_BAO_ContactType::subTypes('Organization'); $fieldTypes = array_merge($fieldTypes, $contactSubType); foreach ($profileFields as $name => $field) { if (in_array($field['field_type'], $fieldTypes)) { list($prefixName, $index) = CRM_Utils_System::explode('-', $name, 2); - if (in_array($prefixName, array('organization_name', 'email')) && empty($field['is_required'])) { + if (in_array($prefixName, ['organization_name', 'email']) && empty($field['is_required'])) { $field['is_required'] = 1; } if (count($form->_submitValues) && @@ -997,18 +1051,18 @@ public function authenticatePledgeUser() { $contactID = CRM_Utils_Request::retrieve('cid', 'Positive', $this); //get pledge status and contact id - $pledgeValues = array(); - $pledgeParams = array('id' => $this->_values['pledge_id']); - $returnProperties = array('contact_id', 'status_id'); + $pledgeValues = []; + $pledgeParams = ['id' => $this->_values['pledge_id']]; + $returnProperties = ['contact_id', 'status_id']; CRM_Core_DAO::commonRetrieve('CRM_Pledge_DAO_Pledge', $pledgeParams, $pledgeValues, $returnProperties); //get all status $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - $validStatus = array( + $validStatus = [ array_search('Pending', $allStatus), array_search('In Progress', $allStatus), array_search('Overdue', $allStatus), - ); + ]; $validUser = FALSE; if ($this->_userID && @@ -1033,7 +1087,7 @@ public function authenticatePledgeUser() { //check for valid pledge status. if (!in_array($pledgeValues['status_id'], $validStatus)) { - CRM_Core_Error::fatal(ts('Oops. You cannot make a payment for this pledge - pledge status is %1.', array(1 => CRM_Utils_Array::value($pledgeValues['status_id'], $allStatus)))); + CRM_Core_Error::fatal(ts('Oops. You cannot make a payment for this pledge - pledge status is %1.', [1 => CRM_Utils_Array::value($pledgeValues['status_id'], $allStatus)])); } } @@ -1068,7 +1122,7 @@ public function cancelRecurring() { * @param bool $isContributionMainPage * Is this the main page? If so add form input fields. * (or better yet don't have this functionality in a function shared with forms that don't share it). - * @param int $selectedMembershipTypeID + * @param int|array $selectedMembershipTypeID * Selected membership id. * @param bool $thankPage * Thank you page. @@ -1087,13 +1141,13 @@ protected function buildMembershipBlock( $separateMembershipPayment = FALSE; if ($this->_membershipBlock) { - $this->_currentMemberships = array(); + $this->_currentMemberships = []; - $membershipTypeIds = $membershipTypes = $radio = array(); + $membershipTypeIds = $membershipTypes = $radio = []; $membershipPriceset = (!empty($this->_priceSetId) && $this->_useForMember) ? TRUE : FALSE; $allowAutoRenewMembership = $autoRenewOption = FALSE; - $autoRenewMembershipTypeOptions = array(); + $autoRenewMembershipTypeOptions = []; $separateMembershipPayment = CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock); @@ -1129,6 +1183,18 @@ protected function buildMembershipBlock( $membershipTypeValues = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIds); $this->_membershipTypeValues = $membershipTypeValues; $endDate = NULL; + + // Check if we support auto-renew on this contribution page + // FIXME: If any of the payment processors do NOT support recurring you cannot setup an + // auto-renew payment even if that processor is not selected. + $allowAutoRenewOpt = TRUE; + if (is_array($this->_paymentProcessors)) { + foreach ($this->_paymentProcessors as $id => $val) { + if ($id && !$val['is_recur']) { + $allowAutoRenewOpt = FALSE; + } + } + } foreach ($membershipTypeIds as $value) { $memType = $membershipTypeValues[$value]; if ($selectedMembershipTypeID != NULL) { @@ -1151,23 +1217,16 @@ protected function buildMembershipBlock( } } elseif ($memType['is_active']) { - $javascriptMethod = NULL; - $allowAutoRenewOpt = (int) $memType['auto_renew']; - if (is_array($this->_paymentProcessors)) { - foreach ($this->_paymentProcessors as $id => $val) { - if ($id && !$val['is_recur']) { - $allowAutoRenewOpt = 0; - continue; - } - } - } - - $javascriptMethod = array('onclick' => "return showHideAutoRenew( this.value );"); - $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = (int) $allowAutoRenewOpt * CRM_Utils_Array::value($value, CRM_Utils_Array::value('auto_renew', $this->_membershipBlock));; if ($allowAutoRenewOpt) { + $javascriptMethod = ['onclick' => "return showHideAutoRenew( this.value );"]; + $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = (int) $memType['auto_renew'] * CRM_Utils_Array::value($value, CRM_Utils_Array::value('auto_renew', $this->_membershipBlock)); $allowAutoRenewMembership = TRUE; } + else { + $javascriptMethod = NULL; + $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = 0; + } //add membership type. $radio[$memType['id']] = $this->createElement('radio', NULL, NULL, NULL, @@ -1224,7 +1283,9 @@ protected function buildMembershipBlock( // Assign autorenew option (0:hide,1:optional,2:required) so we can use it in confirmation etc. $autoRenewOption = CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($this->_priceSetId); - if (isset($membershipTypeValues[$selectedMembershipTypeID]['auto_renew'])) { + //$selectedMembershipTypeID is retrieved as an array for membership priceset if multiple + //options for different organisation is selected on the contribution page. + if (is_numeric($selectedMembershipTypeID) && isset($membershipTypeValues[$selectedMembershipTypeID]['auto_renew'])) { $this->assign('autoRenewOption', $membershipTypeValues[$selectedMembershipTypeID]['auto_renew']); } else { @@ -1240,7 +1301,7 @@ protected function buildMembershipBlock( } elseif ($this->_membershipBlock['is_required'] && count($radio) == 1) { $temp = array_keys($radio); - $this->add('hidden', 'selectMembership', $temp[0], array('id' => 'selectMembership')); + $this->add('hidden', 'selectMembership', $temp[0], ['id' => 'selectMembership']); $this->assign('singleMembership', TRUE); $this->assign('showRadio', FALSE); } @@ -1317,4 +1378,16 @@ protected function setRecurringMembershipParams() { } } + /** + * Get the payment processor object for the submission, returning the manual one for offline payments. + * + * @return CRM_Core_Payment + */ + protected function getPaymentProcessorObject() { + if (!empty($this->_paymentProcessor)) { + return $this->_paymentProcessor['object']; + } + return new CRM_Core_Payment_Manual(); + } + } diff --git a/CRM/Contribute/Form/ContributionCharts.php b/CRM/Contribute/Form/ContributionCharts.php index 8b78eca48d18..158db37c79d4 100644 --- a/CRM/Contribute/Form/ContributionCharts.php +++ b/CRM/Contribute/Form/ContributionCharts.php @@ -1,9 +1,9 @@ addElement('select', 'chart_type', ts('Chart Style'), array( - 'bvg' => ts('Bar'), - 'p3' => ts('Pie'), - ) - ); + $this->addElement('select', 'chart_type', ts('Chart Style'), [ + 'bvg' => ts('Bar'), + 'p3' => ts('Pie'), + ]); $defaultValues['chart_type'] = $this->_chartType; $this->setDefaults($defaultValues); //take available years from database to show in drop down $currentYear = date('Y'); - $years = array(); + $years = []; if (!empty($this->_years)) { if (!array_key_exists($currentYear, $this->_years)) { $this->_years[$currentYear] = $currentYear; @@ -87,9 +86,9 @@ public function buildQuickForm() { } $this->addElement('select', 'select_year', ts('Select Year (for monthly breakdown)'), $years); - $this->setDefaults(array( + $this->setDefaults([ 'select_year' => ($this->_year) ? $this->_year : $currentYear, - )); + ]); } /** @@ -109,7 +108,7 @@ public function postProcess() { //take contribution information monthly $chartInfoMonthly = CRM_Contribute_BAO_Contribution_Utils::contributionChartMonthly($selectedYear); - $chartData = $abbrMonthNames = array(); + $chartData = $abbrMonthNames = []; if (is_array($chartInfoMonthly)) { for ($i = 1; $i <= 12; $i++) { $abbrMonthNames[$i] = strftime('%b', mktime(0, 0, 0, $i, 10, 1970)); @@ -174,11 +173,11 @@ public function postProcess() { $monthlyChart = TRUE; } - $values['divName'] = "open_flash_chart_{$chartKey}"; + $values['divName'] = "chart_{$chartKey}"; $funName = ($chartType == 'bvg') ? 'barChart' : 'pieChart'; // build the chart objects. - $values['object'] = CRM_Utils_OpenFlashChart::$funName($values); + $values['object'] = CRM_Utils_Chart::$funName($values); //build the urls. $urlCnt = 0; @@ -186,7 +185,7 @@ public function postProcess() { $urlParams = NULL; if ($chartKey == 'by_month') { $monthPosition = array_search($index, $abbrMonthNames); - $startDate = CRM_Utils_Date::format(array('Y' => $selectedYear, 'M' => $monthPosition)); + $startDate = CRM_Utils_Date::format(['Y' => $selectedYear, 'M' => $monthPosition]); $endDate = date('Ymd', mktime(0, 0, 0, $monthPosition + 1, 0, $selectedYear)); $urlParams = "reset=1&force=1&status=1&start={$startDate}&end={$endDate}&test=0"; } @@ -196,7 +195,7 @@ public function postProcess() { $endDate = date('Ymd', mktime(0, 0, 0, $config->fiscalYearStart['M'], $config->fiscalYearStart['d'], (substr($index, 0, 4)) + 1)); } else { - $startDate = CRM_Utils_Date::format(array('Y' => substr($index, 0, 4))); + $startDate = CRM_Utils_Date::format(['Y' => substr($index, 0, 4)]); $endDate = date('Ymd', mktime(0, 0, 0, 13, 0, substr($index, 0, 4))); } $urlParams = "reset=1&force=1&status=1&start={$startDate}&end={$endDate}&test=0"; @@ -225,14 +224,14 @@ public function postProcess() { $xSize = 150; } } - $values['size'] = array('xSize' => $xSize, 'ySize' => $ySize); + $values['size'] = ['xSize' => $xSize, 'ySize' => $ySize]; } // finally assign this chart data to template. $this->assign('hasYearlyChart', $yearlyChart); $this->assign('hasByMonthChart', $monthlyChart); - $this->assign('hasOpenFlashChart', empty($chartData) ? FALSE : TRUE); - $this->assign('openFlashChartData', json_encode($chartData)); + $this->assign('hasChart', empty($chartData) ? FALSE : TRUE); + $this->assign('chartData', json_encode($chartData ?? [])); } } diff --git a/CRM/Contribute/Form/ContributionPage.php b/CRM/Contribute/Form/ContributionPage.php index 0952bcb142fe..6cb1b8f6c3ae 100644 --- a/CRM/Contribute/Form/ContributionPage.php +++ b/CRM/Contribute/Form/ContributionPage.php @@ -1,9 +1,9 @@ _action == CRM_Core_Action::UPDATE) { - CRM_Utils_System::setTitle(ts('Configure Page - %1', array(1 => $title))); + $this->setTitle(ts('Configure Page - %1', [1 => $title])); } elseif ($this->_action == CRM_Core_Action::VIEW) { - CRM_Utils_System::setTitle(ts('Preview Page - %1', array(1 => $title))); + $this->setTitle(ts('Preview Page - %1', [1 => $title])); } elseif ($this->_action == CRM_Core_Action::DELETE) { - CRM_Utils_System::setTitle(ts('Delete Page - %1', array(1 => $title))); + $this->setTitle(ts('Delete Page - %1', [1 => $title])); } //cache values. $this->_values = $this->get('values'); if (!is_array($this->_values)) { - $this->_values = array(); + $this->_values = []; if (isset($this->_id) && $this->_id) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; CRM_Core_DAO::commonRetrieve('CRM_Contribute_DAO_ContributionPage', $params, $this->_values); CRM_Contribute_BAO_ContributionPage::setValues($this->_id, $this->_values); } @@ -150,7 +157,7 @@ public function preProcess() { } // Preload libraries required by the "Profiles" tab - $schemas = array('IndividualModel', 'OrganizationModel', 'ContributionModel'); + $schemas = ['IndividualModel', 'OrganizationModel', 'ContributionModel']; if (in_array('CiviMember', CRM_Core_Config::singleton()->enableComponents)) { $schemas[] = 'MembershipModel'; } @@ -176,53 +183,53 @@ public function buildQuickForm() { } if ($this->_single) { - $buttons = array( - array( + $buttons = [ + [ 'type' => 'next', 'name' => ts('Save'), 'spacing' => '         ', 'isDefault' => TRUE, - ), - array( + ], + [ 'type' => 'upload', 'name' => ts('Save and Done'), 'spacing' => '         ', 'subName' => 'done', - ), - ); + ], + ]; if (!$this->_last) { - $buttons[] = array( + $buttons[] = [ 'type' => 'submit', 'name' => ts('Save and Next'), 'spacing' => '                 ', 'subName' => 'savenext', - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'cancel', 'name' => ts('Cancel'), - ); + ]; $this->addButtons($buttons); } else { - $buttons = array(); + $buttons = []; if (!$this->_first) { - $buttons[] = array( + $buttons[] = [ 'type' => 'back', 'name' => ts('Previous'), 'spacing' => '     ', - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'next', 'name' => ts('Continue'), 'spacing' => '         ', 'isDefault' => TRUE, - ); - $buttons[] = array( + ]; + $buttons[] = [ 'type' => 'cancel', 'name' => ts('Cancel'), - ); + ]; $this->addButtons($buttons); } @@ -231,7 +238,7 @@ public function buildQuickForm() { // views are implemented as frozen form if ($this->_action & CRM_Core_Action::VIEW) { $this->freeze(); - $this->addElement('button', 'done', ts('Done'), array('onclick' => "location.href='civicrm/admin/custom/group?reset=1&action=browse'")); + $this->addElement('button', 'done', ts('Done'), ['onclick' => "location.href='civicrm/admin/custom/group?reset=1&action=browse'"]); } // don't show option for contribution amounts section if membership price set @@ -251,7 +258,7 @@ public function buildQuickForm() { } } // set value in DOM that membership price set exists - CRM_Core_Resources::singleton()->addSetting(array('memberPriceset' => $hasMembershipBlk)); + CRM_Core_Resources::singleton()->addSetting(['memberPriceset' => $hasMembershipBlk]); } /** @@ -266,9 +273,9 @@ public function setDefaultValues() { //some child classes calling setdefaults directly w/o preprocess. $this->_values = $this->get('values'); if (!is_array($this->_values)) { - $this->_values = array(); + $this->_values = []; if (isset($this->_id) && $this->_id) { - $params = array('id' => $this->_id); + $params = ['id' => $this->_id]; CRM_Core_DAO::commonRetrieve('CRM_Contribute_DAO_ContributionPage', $params, $this->_values); } $this->set('values', $this->_values); @@ -279,16 +286,16 @@ public function setDefaultValues() { if (isset($this->_id)) { //set defaults for pledgeBlock values. - $pledgeBlockParams = array( + $pledgeBlockParams = [ 'entity_id' => $this->_id, 'entity_table' => ts('civicrm_contribution_page'), - ); - $pledgeBlockDefaults = array(); + ]; + $pledgeBlockDefaults = []; CRM_Pledge_BAO_PledgeBlock::retrieve($pledgeBlockParams, $pledgeBlockDefaults); if ($this->_pledgeBlockID = CRM_Utils_Array::value('id', $pledgeBlockDefaults)) { $defaults['is_pledge_active'] = TRUE; } - $pledgeBlock = array( + $pledgeBlock = [ 'is_pledge_interval', 'max_reminders', 'initial_reminder_day', @@ -296,15 +303,15 @@ public function setDefaultValues() { 'pledge_start_date', 'is_pledge_start_date_visible', 'is_pledge_start_date_editable', - ); + ]; foreach ($pledgeBlock as $key) { $defaults[$key] = CRM_Utils_Array::value($key, $pledgeBlockDefaults); - if ($key == 'pledge_start_date' && CRM_Utils_Array::value($key, $pledgeBlockDefaults)) { + if ($key == 'pledge_start_date' && !empty($pledgeBlockDefaults[$key])) { $defaultPledgeDate = (array) json_decode($pledgeBlockDefaults['pledge_start_date']); - $pledgeDateFields = array( + $pledgeDateFields = [ 'pledge_calendar_date' => 'calendar_date', 'pledge_calendar_month' => 'calendar_month', - ); + ]; $defaults['pledge_default_toggle'] = key($defaultPledgeDate); foreach ($pledgeDateFields as $key => $value) { if (array_key_exists($value, $defaultPledgeDate)) { @@ -332,19 +339,14 @@ public function setDefaultValues() { if ($this->_priceSetID) { $defaults['price_set_id'] = $this->_priceSetID; } - - if (!empty($defaults['end_date'])) { - list($defaults['end_date'], $defaults['end_date_time']) = CRM_Utils_Date::setDateDefaults($defaults['end_date']); - } - - if (!empty($defaults['start_date'])) { - list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults($defaults['start_date']); - } } else { $defaults['is_active'] = 1; // set current date as start date - list($defaults['start_date'], $defaults['start_date_time']) = CRM_Utils_Date::setDateDefaults(); + // @todo look to change to $defaults['start_date'] = date('Ymd His'); + // main settings form overrides this to implement above but this is left here + // 'in case' another extending form uses start_date - for now + $defaults['start_date'] = date('Y-m-d H:i:s'); } if (!empty($defaults['recur_frequency_unit'])) { @@ -353,8 +355,8 @@ public function setDefaultValues() { ), '1'); } else { - # CRM 10860 - $defaults['recur_frequency_unit'] = array('month' => 1); + // CRM-10860 + $defaults['recur_frequency_unit'] = ['month' => 1]; } // confirm page starts out enabled @@ -396,8 +398,7 @@ public function endPostProcess() { switch ($className) { case 'Contribute': $attributes = $this->getVar('_attributes'); - $subPage = strtolower(basename(CRM_Utils_Array::value('action', $attributes))); - $subPageName = ucfirst($subPage); + $subPage = CRM_Utils_Request::retrieveComponent($attributes); if ($subPage == 'friend') { $nextPage = 'custom'; } @@ -408,13 +409,16 @@ public function endPostProcess() { case 'MembershipBlock': $subPage = 'membership'; - $subPageName = 'MembershipBlock'; $nextPage = 'thankyou'; break; + case 'Widget': + $subPage = 'widget'; + $nextPage = 'pcp'; + break; + default: $subPage = strtolower($className); - $subPageName = $className; $nextPage = strtolower($nextPage); if ($subPage == 'amount') { @@ -427,8 +431,8 @@ public function endPostProcess() { } CRM_Core_Session::setStatus(ts("'%1' information has been saved.", - array(1 => $subPageName) - ), ts('Saved'), 'success'); + [1 => CRM_Utils_Array::value('title', CRM_Utils_Array::value($subPage, $this->get('tabHeader')), $className)] + ), $this->getTitle(), 'success'); $this->postProcessHook(); @@ -460,6 +464,7 @@ public function endPostProcess() { * * @return string */ + /** * @return string */ diff --git a/CRM/Contribute/Form/ContributionPage/AddProduct.php b/CRM/Contribute/Form/ContributionPage/AddProduct.php index 5780509c8718..4808e1f73bc3 100644 --- a/CRM/Contribute/Form/ContributionPage/AddProduct.php +++ b/CRM/Contribute/Form/ContributionPage/AddProduct.php @@ -1,9 +1,9 @@ _pid) { $dao = new CRM_Contribute_DAO_PremiumsProduct(); @@ -95,12 +95,12 @@ public function setDefaultValues() { $premiumID = $dao->id; $sql = 'SELECT max( weight ) as max_weight FROM civicrm_premiums_product WHERE premiums_id = %1'; - $params = array(1 => array($premiumID, 'Integer')); + $params = [1 => [$premiumID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); $dao->fetch(); $defaults['weight'] = $dao->max_weight + 1; } - RETURN $defaults; + return $defaults; } /** @@ -123,32 +123,30 @@ public function buildQuickForm() { CRM_Utils_System::redirect($url); } - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Delete'), - 'spacing' => '    ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Delete'), + 'spacing' => '    ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); return; } if ($this->_action & CRM_Core_Action::PREVIEW) { CRM_Contribute_BAO_Premium::buildPremiumPreviewBlock($this, NULL, $this->_pid); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Done with Preview'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Done with Preview'), + 'isDefault' => TRUE, + ], + ]); return; } @@ -161,7 +159,7 @@ public function buildQuickForm() { $this->addElement('text', 'weight', ts('Order'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_PremiumsProduct', 'weight')); $financialType = CRM_Contribute_PseudoConstant::financialType(); - $premiumFinancialType = array(); + $premiumFinancialType = []; CRM_Core_PseudoConstant::populate( $premiumFinancialType, 'CRM_Financial_DAO_EntityFinancialAccount', @@ -171,7 +169,7 @@ public function buildQuickForm() { 'account_relationship = 8' ); - $costFinancialType = array(); + $costFinancialType = []; CRM_Core_PseudoConstant::populate( $costFinancialType, 'CRM_Financial_DAO_EntityFinancialAccount', @@ -195,25 +193,24 @@ public function buildQuickForm() { 'select', 'financial_type_id', ts('Financial Type'), - array('' => ts('- select -')) + $financialType + ['' => ts('- select -')] + $financialType ); $this->addRule('weight', ts('Please enter integer value for weight'), 'integer'); $session->pushUserContext(CRM_Utils_System::url($urlParams, 'action=update&reset=1&id=' . $this->_id)); if ($this->_single) { - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Save'), - 'spacing' => '    ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Save'), + 'spacing' => '    ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } else { parent::buildQuickForm(); @@ -229,15 +226,12 @@ public function postProcess() { $urlParams = 'civicrm/admin/contribute/premium'; if ($this->_action & CRM_Core_Action::PREVIEW) { - $session = CRM_Core_Session::singleton(); $url = CRM_Utils_System::url($urlParams, 'reset=1&action=update&id=' . $this->_id); - $single = $session->get('singleForm'); CRM_Utils_System::redirect($url); return; } if ($this->_action & CRM_Core_Action::DELETE) { - $session = CRM_Core_Session::singleton(); $url = CRM_Utils_System::url($urlParams, 'reset=1&action=update&id=' . $this->_id); $dao = new CRM_Contribute_DAO_PremiumsProduct(); $dao->id = $this->_pid; @@ -246,7 +240,6 @@ public function postProcess() { CRM_Utils_System::redirect($url); } else { - $session = CRM_Core_Session::singleton(); $url = CRM_Utils_System::url($urlParams, 'reset=1&action=update&id=' . $this->_id); if ($this->_pid) { $params['id'] = $this->_pid; @@ -264,7 +257,7 @@ public function postProcess() { } // updateOtherWeights needs to filter on premiums_id - $filter = array('premiums_id' => $params['premiums_id']); + $filter = ['premiums_id' => $params['premiums_id']]; $params['weight'] = CRM_Utils_Weight::updateOtherWeights('CRM_Contribute_DAO_PremiumsProduct', $oldWeight, $params['weight'], $filter); $dao = new CRM_Contribute_DAO_PremiumsProduct(); diff --git a/CRM/Contribute/Form/ContributionPage/Amount.php b/CRM/Contribute/Form/ContributionPage/Amount.php index a950ca545a9f..ef3d9071c586 100644 --- a/CRM/Contribute/Form/ContributionPage/Amount.php +++ b/CRM/Contribute/Form/ContributionPage/Amount.php @@ -1,9 +1,9 @@ addElement('checkbox', 'is_allow_other_amount', ts('Allow other amounts'), NULL, array('onclick' => "minMax(this);showHideAmountBlock( this, 'is_allow_other_amount' );")); - $this->add('text', 'min_amount', ts('Minimum Amount'), array('size' => 8, 'maxlength' => 8)); - $this->addRule('min_amount', ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('9.99', ' '))), 'money'); + $this->addElement('checkbox', 'is_allow_other_amount', ts('Allow other amounts'), NULL, ['onclick' => "minMax(this);showHideAmountBlock( this, 'is_allow_other_amount' );"]); + $this->add('text', 'min_amount', ts('Minimum Amount'), ['size' => 8, 'maxlength' => 8]); + $this->addRule('min_amount', ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('9.99', ' ')]), 'money'); - $this->add('text', 'max_amount', ts('Maximum Amount'), array('size' => 8, 'maxlength' => 8)); - $this->addRule('max_amount', ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('99.99', ' '))), 'money'); + $this->add('text', 'max_amount', ts('Maximum Amount'), ['size' => 8, 'maxlength' => 8]); + $this->addRule('max_amount', ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('99.99', ' ')]), 'money'); //CRM-12055 $this->add('text', 'amount_label', ts('Contribution Amounts Label')); - $default = array($this->createElement('radio', NULL, NULL, NULL, 0)); - $this->add('hidden', "price_field_id", '', array('id' => "price_field_id")); - $this->add('hidden', "price_field_other", '', array('id' => "price_field_option")); + $default = [$this->createElement('radio', NULL, NULL, NULL, 0)]; + $this->add('hidden', "price_field_id", '', ['id' => "price_field_id"]); + $this->add('hidden', "price_field_other", '', ['id' => "price_field_option"]); for ($i = 1; $i <= self::NUM_OPTION; $i++) { // label $this->add('text', "label[$i]", ts('Label'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'label')); - $this->add('hidden', "price_field_value[$i]", '', array('id' => "price_field_value[$i]")); + $this->add('hidden', "price_field_value[$i]", '', ['id' => "price_field_value[$i]"]); // value $this->add('text', "value[$i]", ts('Value'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'value')); - $this->addRule("value[$i]", ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('99.99', ' '))), 'money'); + $this->addRule("value[$i]", ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('99.99', ' ')]), 'money'); // default $default[] = $this->createElement('radio', NULL, NULL, NULL, $i); @@ -83,22 +83,22 @@ public function buildQuickForm() { $this->addGroup($default, 'default'); - $this->addElement('checkbox', 'amount_block_is_active', ts('Contribution Amounts section enabled'), NULL, array('onclick' => "showHideAmountBlock( this, 'amount_block_is_active' );")); + $this->addElement('checkbox', 'amount_block_is_active', ts('Contribution Amounts section enabled'), NULL, ['onclick' => "showHideAmountBlock( this, 'amount_block_is_active' );"]); $this->addElement('checkbox', 'is_monetary', ts('Execute real-time monetary transactions')); $paymentProcessors = CRM_Financial_BAO_PaymentProcessor::getAllPaymentProcessors('live'); - $recurringPaymentProcessor = $futurePaymentProcessor = $paymentProcessor = array(); + $recurringPaymentProcessor = $futurePaymentProcessor = $paymentProcessor = []; if (!empty($paymentProcessors)) { foreach ($paymentProcessors as $id => $processor) { if ($id != 0) { $paymentProcessor[$id] = $processor['name']; } - if (CRM_Utils_Array::value('is_recur', $processor)) { + if (!empty($processor['is_recur'])) { $recurringPaymentProcessor[] = $id; } - if (CRM_Utils_Array::value('object', $processor) && $processor['object']->supports('FutureRecurStartDate')) { + if (!empty($processor['object']) && $processor['object']->supports('FutureRecurStartDate')) { $futurePaymentProcessor[] = $id; } } @@ -116,18 +116,18 @@ public function buildQuickForm() { $this->addCheckBox('payment_processor', ts('Payment Processor'), array_flip($paymentProcessor), NULL, NULL, NULL, NULL, - array('  ', '  ', '  ', '
    ') + ['  ', '  ', '  ', '
    '] ); //check if selected payment processor supports recurring payment if (!empty($recurringPaymentProcessor)) { $this->addElement('checkbox', 'is_recur', ts('Recurring Contributions'), NULL, - array('onclick' => "showHideByValue('is_recur',true,'recurFields','table-row','radio',false);") + ['onclick' => "showHideByValue('is_recur',true,'recurFields','table-row','radio',false);"] ); $this->addCheckBox('recur_frequency_unit', ts('Supported recurring units'), CRM_Core_OptionGroup::values('recur_frequency_units', FALSE, FALSE, TRUE), NULL, NULL, NULL, NULL, - array('  ', '  ', '  ', '
    '), TRUE + ['  ', '  ', '  ', '
    '], TRUE ); $this->addElement('checkbox', 'is_recur_interval', ts('Support recurring intervals')); $this->addElement('checkbox', 'is_recur_installments', ts('Offer installments')); @@ -152,42 +152,43 @@ public function buildQuickForm() { else { $this->assign('price', TRUE); } - $this->add('select', 'price_set_id', ts('Price Set'), - array( - '' => ts('- none -'), - ) + $price, - NULL, array('onchange' => "showHideAmountBlock( this.value, 'price_set_id' );") - ); + + $this->addField('price_set_id', [ + 'entity' => 'PriceSet', + 'options' => $price, + 'onchange' => "showHideAmountBlock( this.value, 'price_set_id' );", + ]); + //CiviPledge fields. $config = CRM_Core_Config::singleton(); if (in_array('CiviPledge', $config->enableComponents)) { $this->assign('civiPledge', TRUE); $this->addElement('checkbox', 'is_pledge_active', ts('Pledges'), - NULL, array('onclick' => "showHideAmountBlock( this, 'is_pledge_active' ); return showHideByValue('is_pledge_active',true,'pledgeFields','table-row','radio',false);") + NULL, ['onclick' => "showHideAmountBlock( this, 'is_pledge_active' ); return showHideByValue('is_pledge_active',true,'pledgeFields','table-row','radio',false);"] ); $this->addCheckBox('pledge_frequency_unit', ts('Supported pledge frequencies'), CRM_Core_OptionGroup::values('recur_frequency_units', FALSE, FALSE, TRUE), NULL, NULL, NULL, NULL, - array('  ', '  ', '  ', '
    '), TRUE + ['  ', '  ', '  ', '
    '], TRUE ); $this->addElement('checkbox', 'is_pledge_interval', ts('Allow frequency intervals')); - $this->addElement('text', 'initial_reminder_day', ts('Send payment reminder'), array('size' => 3)); - $this->addElement('text', 'max_reminders', ts('Send up to'), array('size' => 3)); - $this->addElement('text', 'additional_reminder_day', ts('Send additional reminders'), array('size' => 3)); + $this->addElement('text', 'initial_reminder_day', ts('Send payment reminder'), ['size' => 3]); + $this->addElement('text', 'max_reminders', ts('Send up to'), ['size' => 3]); + $this->addElement('text', 'additional_reminder_day', ts('Send additional reminders'), ['size' => 3]); if (!empty($futurePaymentProcessor)) { // CRM-18854 $this->addElement('checkbox', 'adjust_recur_start_date', ts('Adjust Recurring Start Date'), NULL, - array('onclick' => "showHideByValue('adjust_recur_start_date',true,'recurDefaults','table-row','radio',false);") + ['onclick' => "showHideByValue('adjust_recur_start_date',true,'recurDefaults','table-row','radio',false);"] ); - $this->addDate('pledge_calendar_date', ts('Specific Calendar Date')); + $this->add('datepicker', 'pledge_calendar_date', ts('Specific Calendar Date'), [], FALSE, ['time' => FALSE]); $month = CRM_Utils_Date::getCalendarDayOfMonth(); $this->add('select', 'pledge_calendar_month', ts('Specific day of Month'), $month); - $pledgeDefaults = array( + $pledgeDefaults = [ 'contribution_date' => ts('Day of Contribution'), 'calendar_date' => ts('Specific Calendar Date'), 'calendar_month' => ts('Specific day of Month'), - ); - $this->addRadio('pledge_default_toggle', ts('Recurring Contribution Start Date Default'), $pledgeDefaults, array('allowClear' => FALSE), '

    '); + ]; + $this->addRadio('pledge_default_toggle', ts('Recurring Contribution Start Date Default'), $pledgeDefaults, ['allowClear' => FALSE], '

    '); $this->addElement('checkbox', 'is_pledge_start_date_visible', ts('Show Recurring Donation Start Date?'), NULL); $this->addElement('checkbox', 'is_pledge_start_date_editable', ts('Allow Edits to Recurring Donation Start date?'), NULL); } @@ -196,7 +197,7 @@ public function buildQuickForm() { //add currency element. $this->addCurrency('currency', ts('Currency')); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Amount', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_Amount', 'formRule'], $this); parent::buildQuickForm(); } @@ -221,14 +222,14 @@ public function setDefaultValues() { if ($isQuick = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config')) { $this->assign('isQuick', $isQuick); //$priceField = CRM_Core_DAO::getFieldValue( 'CRM_Price_DAO_PriceField', $priceSetId, 'id', 'price_set_id' ); - $options = $pFIDs = array(); - $priceFieldParams = array('price_set_id' => $priceSetId); - $priceFields = CRM_Core_DAO::commonRetrieveAll('CRM_Price_DAO_PriceField', 'price_set_id', $priceSetId, $pFIDs, $return = array( - 'html_type', - 'name', - 'is_active', - 'label', - )); + $options = $pFIDs = []; + $priceFieldParams = ['price_set_id' => $priceSetId]; + $priceFields = CRM_Core_DAO::commonRetrieveAll('CRM_Price_DAO_PriceField', 'price_set_id', $priceSetId, $pFIDs, $return = [ + 'html_type', + 'name', + 'is_active', + 'label', + ]); foreach ($priceFields as $priceField) { if ($priceField['id'] && $priceField['html_type'] == 'Radio' && $priceField['name'] == 'contribution_amount') { $defaults['price_field_id'] = $priceField['id']; @@ -304,7 +305,7 @@ public function setDefaultValues() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; //as for separate membership payment we has to have //contribution amount section enabled, hence to disable it need to //check if separate membership payment enabled, @@ -344,14 +345,14 @@ public static function formRule($fields, $files, $self) { } // CRM-18854 Check if recurring start date is in the future. - if (CRM_Utils_Array::value('pledge_calendar_date', $fields)) { + if (!empty($fields['pledge_calendar_date'])) { if (date('Ymd') > date('Ymd', strtotime($fields['pledge_calendar_date']))) { $errors['pledge_calendar_date'] = ts('The recurring start date cannot be prior to the current date.'); } } //check for the amount label (mandatory) - if (!empty($fields['amount_block_is_active']) && empty($fields['amount_label'])) { + if (!empty($fields['amount_block_is_active']) && empty($fields['price_set_id']) && empty($fields['amount_label'])) { $errors['amount_label'] = ts('Please enter the contribution amount label.'); } $minAmount = CRM_Utils_Array::value('min_amount', $fields); @@ -372,6 +373,11 @@ public static function formRule($fields, $files, $self) { $errors['pay_later_receipt'] = ts('Please enter the instructions to be sent to the contributor when they choose to \'pay later\'.'); } } + else { + if ($fields['amount_block_is_active'] && empty($fields['payment_processor'])) { + $errors['payment_processor'] = ts('You have listed fixed contribution options or selected a price set, but no payment option has been selected. Please select at least one payment processor and/or enable the pay later option.'); + } + } // don't allow price set w/ membership signup, CRM-5095 if ($priceSetId = CRM_Utils_Array::value('price_set_id', $fields)) { @@ -423,17 +429,6 @@ public static function formRule($fields, $files, $self) { $errors['payment_processor'] = ts("Financial Account of account relationship of 'Expense Account is' is not configured for Financial Type : ") . $financialType; } - if (!empty($fields['is_recur_interval'])) { - foreach (array_keys($fields['payment_processor']) as $paymentProcessorID) { - $paymentProcessorTypeId = CRM_Core_DAO::getFieldValue( - 'CRM_Financial_DAO_PaymentProcessor', - $paymentProcessorID, - 'payment_processor_type_id' - ); - $paymentProcessorType = CRM_Core_PseudoConstant::paymentProcessorType(FALSE, $paymentProcessorTypeId, 'name'); - } - } - return $errors; } @@ -463,7 +458,7 @@ public function postProcess() { $priceSetID = CRM_Utils_Array::value('price_set_id', $params); // get required fields. - $fields = array( + $fields = [ 'id' => $this->_id, 'is_recur' => FALSE, 'min_amount' => "null", @@ -477,14 +472,14 @@ public function postProcess() { 'default_amount_id' => "null", 'is_allow_other_amount' => FALSE, 'amount_block_is_active' => FALSE, - ); - $resetFields = array(); + ]; + $resetFields = []; if ($priceSetID) { - $resetFields = array('min_amount', 'max_amount', 'is_allow_other_amount'); + $resetFields = ['min_amount', 'max_amount', 'is_allow_other_amount']; } if (empty($params['is_recur'])) { - $resetFields = array_merge($resetFields, array('is_recur_interval', 'recur_frequency_unit')); + $resetFields = array_merge($resetFields, ['is_recur_interval', 'recur_frequency_unit']); } foreach ($fields as $field => $defaultVal) { @@ -493,10 +488,10 @@ public function postProcess() { $val = $defaultVal; } - if (in_array($field, array( + if (in_array($field, [ 'min_amount', 'max_amount', - ))) { + ])) { $val = CRM_Utils_Rule::cleanMoney($val); } @@ -511,19 +506,19 @@ public function postProcess() { $params['is_recur_installments'] = CRM_Utils_Array::value('is_recur_installments', $params, FALSE); } - if (CRM_Utils_Array::value('adjust_recur_start_date', $params)) { + if (!empty($params['adjust_recur_start_date'])) { $fieldValue = ''; - $pledgeDateFields = array( + $pledgeDateFields = [ 'calendar_date' => 'pledge_calendar_date', 'calendar_month' => 'pledge_calendar_month', - ); + ]; if ($params['pledge_default_toggle'] == 'contribution_date') { - $fieldValue = json_encode(array('contribution_date' => date('m/d/Y'))); + $fieldValue = json_encode(['contribution_date' => date('Y-m-d')]); } else { foreach ($pledgeDateFields as $key => $pledgeDateField) { - if (CRM_Utils_Array::value($pledgeDateField, $params) && $params['pledge_default_toggle'] == $key) { - $fieldValue = json_encode(array($key => $params[$pledgeDateField])); + if (!empty($params[$pledgeDateField]) && $params['pledge_default_toggle'] == $key) { + $fieldValue = json_encode([$key => $params[$pledgeDateField]]); break; } } @@ -536,10 +531,10 @@ public function postProcess() { $params['is_pledge_start_date_visible'] = 0; $params['is_pledge_start_date_editable'] = 0; } - if (!CRM_Utils_Array::value('is_pledge_start_date_visible', $params)) { + if (empty($params['is_pledge_start_date_visible'])) { $params['is_pledge_start_date_visible'] = 0; } - if (!CRM_Utils_Array::value('is_pledge_start_date_editable', $params)) { + if (empty($params['is_pledge_start_date_editable'])) { $params['is_pledge_start_date_editable'] = 0; } @@ -590,18 +585,19 @@ public function postProcess() { $values = CRM_Utils_Array::value('value', $params); $default = CRM_Utils_Array::value('default', $params); - $options = array(); + $options = []; for ($i = 1; $i < self::NUM_OPTION; $i++) { if (isset($values[$i]) && (strlen(trim($values[$i])) > 0) ) { - $options[] = array( + $values[$i] = $params['value'][$i] = CRM_Utils_Rule::cleanMoney(trim($values[$i])); + $options[] = [ 'label' => trim($labels[$i]), - 'value' => CRM_Utils_Rule::cleanMoney(trim($values[$i])), + 'value' => $values[$i], 'weight' => $i, 'is_active' => 1, 'is_default' => $default == $i, - ); + ]; } } /* || !empty($params['price_field_value']) || CRM_Utils_Array::value( 'price_field_other', $params )*/ @@ -655,11 +651,11 @@ public function postProcess() { } CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $this->_id, $priceSetId); if (!empty($options)) { - $editedFieldParams = array( + $editedFieldParams = [ 'price_set_id' => $priceSetId, 'name' => 'contribution_amount', - ); - $editedResults = array(); + ]; + $editedResults = []; $noContriAmount = 1; CRM_Price_BAO_PriceField::retrieve($editedFieldParams, $editedResults); if (empty($editedResults['id'])) { @@ -691,16 +687,16 @@ public function postProcess() { $priceField = CRM_Price_BAO_PriceField::create($fieldParams); } if (!empty($params['is_allow_other_amount']) && empty($params['price_field_other'])) { - $editedFieldParams = array( + $editedFieldParams = [ 'price_set_id' => $priceSetId, 'name' => 'other_amount', - ); - $editedResults = array(); + ]; + $editedResults = []; CRM_Price_BAO_PriceField::retrieve($editedFieldParams, $editedResults); if (!$priceFieldID = CRM_Utils_Array::value('id', $editedResults)) { - $fieldParams = array( + $fieldParams = [ 'name' => 'other_amount', 'label' => ts('Other Amount'), 'price_set_id' => $priceSetId, @@ -708,7 +704,7 @@ public function postProcess() { 'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $this->_values), 'is_display_amounts' => 0, 'weight' => 3, - ); + ]; $fieldParams['option_weight'][1] = 1; $fieldParams['option_amount'][1] = 1; if (!$noContriAmount) { @@ -728,11 +724,11 @@ public function postProcess() { if (!$noContriAmount) { $priceFieldValueID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldID, 'id', 'price_field_id'); CRM_Core_DAO::setFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldValueID, 'label', $params['amount_label']); - $fieldParams = array( + $fieldParams = [ 'is_required' => 1, 'label' => $params['amount_label'], 'id' => $priceFieldID, - ); + ]; } $fieldParams['is_active'] = 1; $priceField = CRM_Price_BAO_PriceField::add($fieldParams); @@ -745,11 +741,11 @@ public function postProcess() { elseif ($priceFieldID = CRM_Utils_Array::value('price_field_other', $params)) { $priceFieldValueID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldID, 'id', 'price_field_id'); if (!$noContriAmount) { - $fieldParams = array( + $fieldParams = [ 'is_required' => 1, 'label' => $params['amount_label'], 'id' => $priceFieldID, - ); + ]; CRM_Price_BAO_PriceField::add($fieldParams); CRM_Core_DAO::setFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldValueID, 'label', $params['amount_label']); } @@ -762,14 +758,14 @@ public function postProcess() { if (!empty($params['is_pledge_active'])) { $deletePledgeBlk = FALSE; - $pledgeBlockParams = array( + $pledgeBlockParams = [ 'entity_id' => $contributionPageID, 'entity_table' => ts('civicrm_contribution_page'), - ); + ]; if ($this->_pledgeBlockID) { $pledgeBlockParams['id'] = $this->_pledgeBlockID; } - $pledgeBlock = array( + $pledgeBlock = [ 'pledge_frequency_unit', 'max_reminders', 'initial_reminder_day', @@ -777,7 +773,7 @@ public function postProcess() { 'pledge_start_date', 'is_pledge_start_date_visible', 'is_pledge_start_date_editable', - ); + ]; foreach ($pledgeBlock as $key) { $pledgeBlockParams[$key] = CRM_Utils_Array::value($key, $params); } diff --git a/CRM/Contribute/Form/ContributionPage/Custom.php b/CRM/Contribute/Form/ContributionPage/Custom.php index 930a29512930..94f02e99c194 100644 --- a/CRM/Contribute/Form/ContributionPage/Custom.php +++ b/CRM/Contribute/Form/ContributionPage/Custom.php @@ -1,9 +1,9 @@ 'contact_1', 'entity_type' => 'IndividualModel'); - $allowCoreTypes = array_merge(array('Contact', 'Individual'), CRM_Contact_BAO_ContactType::subTypes('Individual')); - $allowSubTypes = array(); + $entities = []; + $entities[] = ['entity_name' => 'contact_1', 'entity_type' => 'IndividualModel']; + $allowCoreTypes = array_merge(['Contact', 'Individual'], CRM_Contact_BAO_ContactType::subTypes('Individual')); + $allowSubTypes = []; // Register 'contribution_1' $financialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', $this->_id, 'financial_type_id'); $allowCoreTypes[] = 'Contribution'; //CRM-15427 - $allowSubTypes['ContributionType'] = array($financialTypeId); - $entities[] = array( + $allowSubTypes['ContributionType'] = [$financialTypeId]; + $entities[] = [ 'entity_name' => 'contribution_1', 'entity_type' => 'ContributionModel', 'entity_sub_type' => '*', - ); + ]; // If applicable, register 'membership_1' $member = CRM_Member_BAO_Membership::getMembershipBlock($this->_id); if ($member && $member['is_active']) { //CRM-15427 - $entities[] = array( + $entities[] = [ 'entity_name' => 'membership_1', 'entity_type' => 'MembershipModel', 'entity_sub_type' => '*', - ); + ]; $allowCoreTypes[] = 'Membership'; $allowSubTypes['MembershipType'] = explode(',', $member['membership_types']); } @@ -73,7 +73,7 @@ public function buildQuickForm() { $this->addProfileSelector('custom_pre_id', ts('Include Profile') . '
    ' . ts('(top of page)'), $allowCoreTypes, $allowSubTypes, $entities, TRUE); $this->addProfileSelector('custom_post_id', ts('Include Profile') . '
    ' . ts('(bottom of page)'), $allowCoreTypes, $allowSubTypes, $entities, TRUE); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Custom', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_Custom', 'formRule'], $this); parent::buildQuickForm(); } @@ -106,12 +106,12 @@ public function postProcess() { $transaction = new CRM_Core_Transaction(); // also update uf join table - $ufJoinParams = array( + $ufJoinParams = [ 'is_active' => 1, 'module' => 'CiviContribute', 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $this->_id, - ); + ]; // first delete all past entries CRM_Core_BAO_UFJoin::deleteAll($ufJoinParams); @@ -156,7 +156,7 @@ public function getTitle() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $form) { - $errors = array(); + $errors = []; $preProfileType = $postProfileType = NULL; // for membership profile make sure Membership section is enabled // get membership section for this contribution page diff --git a/CRM/Contribute/Form/ContributionPage/Delete.php b/CRM/Contribute/Form/ContributionPage/Delete.php index 22d186344fb2..a715b136d8b2 100644 --- a/CRM/Contribute/Form/ContributionPage/Delete.php +++ b/CRM/Contribute/Form/ContributionPage/Delete.php @@ -1,9 +1,9 @@ _relatedContributions) { - $buttons[] = array( + $buttons[] = [ 'type' => 'next', 'name' => ts('Delete Contribution Page'), 'isDefault' => TRUE, - ); + ]; } - $buttons[] = array( + $buttons[] = [ 'type' => 'cancel', 'name' => ts('Cancel'), - ); + ]; $this->addButtons($buttons); } @@ -105,10 +106,10 @@ public function postProcess() { // first delete the join entries associated with this contribution page $dao = new CRM_Core_DAO_UFJoin(); - $params = array( + $params = [ 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $this->_id, - ); + ]; $dao->copyValues($params); $dao->delete(); @@ -137,7 +138,7 @@ public function postProcess() { $transaction->commit(); - CRM_Core_Session::setStatus(ts("The contribution page '%1' has been deleted.", array(1 => $this->_title)), ts('Deleted'), 'success'); + CRM_Core_Session::setStatus(ts("The contribution page '%1' has been deleted.", [1 => $this->_title]), ts('Deleted'), 'success'); } } diff --git a/CRM/Contribute/Form/ContributionPage/Premium.php b/CRM/Contribute/Form/ContributionPage/Premium.php index f5c3bbe9b9c0..109dbc39edda 100644 --- a/CRM/Contribute/Form/ContributionPage/Premium.php +++ b/CRM/Contribute/Form/ContributionPage/Premium.php @@ -1,9 +1,9 @@ _id)) { $dao = new CRM_Contribute_DAO_Premium(); $dao->entity_table = 'civicrm_contribution_page'; @@ -74,7 +74,7 @@ public function buildQuickForm() { // CRM-10999 Control label and position for No Thank-you radio button $this->add('text', 'premiums_nothankyou_label', ts('No Thank-you Label'), $attributes['premiums_nothankyou_label']); - $positions = array(1 => ts('Before Premiums'), 2 => ts('After Premiums')); + $positions = [1 => ts('Before Premiums'), 2 => ts('After Premiums')]; $this->add('select', 'premiums_nothankyou_position', ts('No Thank-you Option'), $positions); $showForm = TRUE; @@ -92,7 +92,7 @@ public function buildQuickForm() { $this->assign('showForm', $showForm); parent::buildQuickForm(); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Premium', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_Premium', 'formRule'], $this); $premiumPage = new CRM_Contribute_Page_Premium(); $premiumPage->browse(); @@ -108,7 +108,7 @@ public function buildQuickForm() { * mixed true or array of errors */ public static function formRule($params) { - $errors = array(); + $errors = []; if (!empty($params['premiums_active'])) { if (empty($params['premiums_nothankyou_label'])) { $errors['premiums_nothankyou_label'] = ts('No Thank-you Label is a required field.'); diff --git a/CRM/Contribute/Form/ContributionPage/Settings.php b/CRM/Contribute/Form/ContributionPage/Settings.php index af346e884bec..55fe85b695d1 100644 --- a/CRM/Contribute/Form/ContributionPage/Settings.php +++ b/CRM/Contribute/Form/ContributionPage/Settings.php @@ -1,9 +1,9 @@ _id) { + $defaults['start_date'] = date('Y-m-d H:i:s'); + unset($defaults['start_time']); + } $soft_credit_types = CRM_Core_OptionGroup::values('soft_credit_type', TRUE, FALSE, FALSE, NULL, 'name'); if ($this->_id) { @@ -53,7 +58,7 @@ public function setDefaultValues() { ); CRM_Utils_System::setTitle(ts('Title and Settings') . " ($title)"); - foreach (array('on_behalf', 'soft_credit') as $module) { + foreach (['on_behalf', 'soft_credit'] as $module) { $ufJoinDAO = new CRM_Core_DAO_UFJoin(); $ufJoinDAO->module = $module; $ufJoinDAO->entity_id = $this->_id; @@ -78,10 +83,10 @@ public function setDefaultValues() { if ($ufGroupDAO->find(TRUE)) { $defaults['honoree_profile'] = $ufGroupDAO->id; } - $defaults['soft_credit_types'] = array( + $defaults['soft_credit_types'] = [ CRM_Utils_Array::value('in_honor_of', $soft_credit_types), CRM_Utils_Array::value('in_memory_of', $soft_credit_types), - ); + ]; } else { $ufGroupDAO = new CRM_Core_DAO_UFGroup(); @@ -101,10 +106,10 @@ public function setDefaultValues() { if ($ufGroupDAO->find(TRUE)) { $defaults['honoree_profile'] = $ufGroupDAO->id; } - $defaults['soft_credit_types'] = array( + $defaults['soft_credit_types'] = [ CRM_Utils_Array::value('in_honor_of', $soft_credit_types), CRM_Utils_Array::value('in_memory_of', $soft_credit_types), - ); + ]; } return $defaults; @@ -120,9 +125,9 @@ public function buildQuickForm() { // financial Type CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::ADD); - $financialOptions = array( + $financialOptions = [ 'options' => $financialTypes, - ); + ]; if (!CRM_Core_Permission::check('administer CiviCRM Financial Types')) { $financialOptions['context'] = 'search'; } @@ -130,6 +135,7 @@ public function buildQuickForm() { // name $this->add('text', 'title', ts('Title'), $attributes['title'], TRUE); + $this->addField('contribution_page_frontend_title', ['entity' => 'ContributionPage']); //CRM-7362 --add campaigns. CRM_Campaign_BAO_Campaign::addCampaign($this, CRM_Utils_Array::value('campaign_id', $this->_values)); @@ -139,42 +145,42 @@ public function buildQuickForm() { $this->add('wysiwyg', 'footer_text', ts('Footer Message'), $attributes['footer_text']); //Register schema which will be used for OnBehalOf and HonorOf profile Selector - CRM_UF_Page_ProfileEditor::registerSchemas(array('OrganizationModel', 'HouseholdModel')); + CRM_UF_Page_ProfileEditor::registerSchemas(['OrganizationModel', 'HouseholdModel']); // is on behalf of an organization ? - $this->addElement('checkbox', 'is_organization', ts('Allow individuals to contribute and / or signup for membership on behalf of an organization?'), NULL, array('onclick' => "showHideByValue('is_organization',true,'for_org_text','table-row','radio',false);showHideByValue('is_organization',true,'for_org_option','table-row','radio',false);")); + $this->addElement('checkbox', 'is_organization', ts('Allow individuals to contribute and / or signup for membership on behalf of an organization?'), NULL, ['onclick' => "showHideByValue('is_organization',true,'for_org_text','table-row','radio',false);showHideByValue('is_organization',true,'for_org_option','table-row','radio',false);"]); //CRM-15787 - If applicable, register 'membership_1' $member = CRM_Member_BAO_Membership::getMembershipBlock($this->_id); - $coreTypes = array('Contact', 'Organization'); + $coreTypes = ['Contact', 'Organization']; - $entities[] = array( - 'entity_name' => array('contact_1'), + $entities[] = [ + 'entity_name' => ['contact_1'], 'entity_type' => 'OrganizationModel', - ); + ]; if ($member && $member['is_active']) { $coreTypes[] = 'Membership'; - $entities[] = array( - 'entity_name' => array('membership_1'), + $entities[] = [ + 'entity_name' => ['membership_1'], 'entity_type' => 'MembershipModel', - ); + ]; } $allowCoreTypes = array_merge($coreTypes, CRM_Contact_BAO_ContactType::subTypes('Organization')); - $allowSubTypes = array(); + $allowSubTypes = []; $this->addProfileSelector('onbehalf_profile_id', ts('Organization Profile'), $allowCoreTypes, $allowSubTypes, $entities); - $options = array(); + $options = []; $options[] = $this->createElement('radio', NULL, NULL, ts('Optional'), 1); $options[] = $this->createElement('radio', NULL, NULL, ts('Required'), 2); $this->addGroup($options, 'is_for_organization', ''); - $this->add('textarea', 'for_organization', ts('On behalf of Label'), array('rows' => 2, 'cols' => 50)); + $this->add('textarea', 'for_organization', ts('On behalf of Label'), ['rows' => 2, 'cols' => 50]); // collect goal amount - $this->add('text', 'goal_amount', ts('Goal Amount'), array('size' => 8, 'maxlength' => 12)); - $this->addRule('goal_amount', ts('Please enter a valid money value (e.g. %1).', array(1 => CRM_Utils_Money::format('99.99', ' '))), 'money'); + $this->add('text', 'goal_amount', ts('Goal Amount'), ['size' => 8, 'maxlength' => 12]); + $this->addRule('goal_amount', ts('Please enter a valid money value (e.g. %1).', [1 => CRM_Utils_Money::format('99.99', ' ')]), 'money'); // is confirmation page enabled? $this->addElement('checkbox', 'is_confirm_enabled', ts('Use a confirmation page?')); @@ -186,34 +192,34 @@ public function buildQuickForm() { $this->addElement('checkbox', 'is_active', ts('Is this Online Contribution Page Active?')); // should the honor be enabled - $this->addElement('checkbox', 'honor_block_is_active', ts('Honoree Section Enabled'), NULL, array('onclick' => "showHonor()")); + $this->addElement('checkbox', 'honor_block_is_active', ts('Honoree Section Enabled'), NULL, ['onclick' => "showHonor()"]); - $this->add('text', 'honor_block_title', ts('Honoree Section Title'), array('maxlength' => 255, 'size' => 45)); + $this->add('text', 'honor_block_title', ts('Honoree Section Title'), ['maxlength' => 255, 'size' => 45]); - $this->add('textarea', 'honor_block_text', ts('Honoree Introductory Message'), array('rows' => 2, 'cols' => 50)); + $this->add('textarea', 'honor_block_text', ts('Honoree Introductory Message'), ['rows' => 2, 'cols' => 50]); - $this->addSelect('soft_credit_types', array( + $this->addSelect('soft_credit_types', [ 'label' => ts('Honor Types'), 'entity' => 'ContributionSoft', 'field' => 'soft_credit_type_id', 'multiple' => TRUE, 'class' => 'huge', - )); + ]); - $entities = array( - array( + $entities = [ + [ 'entity_name' => 'contact_1', 'entity_type' => 'IndividualModel', - ), - ); + ], + ]; - $allowCoreTypes = array_merge(array( - 'Contact', - 'Individual', - 'Organization', - 'Household', - ), CRM_Contact_BAO_ContactType::subTypes('Individual')); - $allowSubTypes = array(); + $allowCoreTypes = array_merge([ + 'Contact', + 'Individual', + 'Organization', + 'Household', + ], CRM_Contact_BAO_ContactType::subTypes('Individual')); + $allowSubTypes = []; $this->addProfileSelector('honoree_profile', ts('Honoree Profile'), $allowCoreTypes, $allowSubTypes, $entities); @@ -223,10 +229,10 @@ public function buildQuickForm() { } // add optional start and end dates - $this->addDateTime('start_date', ts('Start Date')); - $this->addDateTime('end_date', ts('End Date')); + $this->add('datepicker', 'start_date', ts('Start Date')); + $this->add('datepicker', 'end_date', ts('End Date')); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Settings', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_Settings', 'formRule'], $this); parent::buildQuickForm(); } @@ -244,7 +250,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($values, $files, $self) { - $errors = array(); + $errors = []; $contributionPageId = $self->_id; //CRM-4286 if (strstr($values['title'], '/')) { @@ -257,7 +263,7 @@ public static function formRule($values, $files, $self) { $errors['onbehalf_profile_id'] = ts('Please select a profile to collect organization information on this contribution page.'); } else { - $requiredProfileFields = array('organization_name', 'email'); + $requiredProfileFields = ['organization_name', 'email']; if (!CRM_Core_BAO_UFGroup::checkValidProfile($values['onbehalf_profile_id'], $requiredProfileFields)) { $errors['onbehalf_profile_id'] = ts('Profile does not contain the minimum required fields for an On Behalf Of Organization'); } @@ -278,11 +284,11 @@ public static function formRule($values, $files, $self) { //dont allow on behalf of save when //pre or post profile consists of membership fields if ($contributionPageId && !empty($values['is_organization'])) { - $ufJoinParams = array( + $ufJoinParams = [ 'module' => 'CiviContribute', 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $contributionPageId, - ); + ]; list($contributionProfiles['custom_pre_id'], $contributionProfiles['custom_post_id'] @@ -303,7 +309,7 @@ public static function formRule($values, $files, $self) { } } if (!empty($conProfileType)) { - $errors['is_organization'] = ts("You should move the membership related fields configured in %1 to the 'On Behalf' profile for this Contribution Page", array(1 => $conProfileType)); + $errors['is_organization'] = ts("You should move the membership related fields configured in %1 to the 'On Behalf' profile for this Contribution Page", [1 => $conProfileType]); } } return $errors; @@ -334,10 +340,6 @@ public function postProcess() { $params['is_credit_card_only'] = CRM_Utils_Array::value('is_credit_card_only', $params, FALSE); $params['honor_block_is_active'] = CRM_Utils_Array::value('honor_block_is_active', $params, FALSE); $params['is_for_organization'] = !empty($params['is_organization']) ? CRM_Utils_Array::value('is_for_organization', $params, FALSE) : 0; - - $params['start_date'] = CRM_Utils_Date::processDate($params['start_date'], $params['start_date_time'], TRUE); - $params['end_date'] = CRM_Utils_Date::processDate($params['end_date'], $params['end_date_time'], TRUE); - $params['goal_amount'] = CRM_Utils_Rule::cleanMoney($params['goal_amount']); if (!$params['honor_block_is_active']) { @@ -347,18 +349,18 @@ public function postProcess() { $dao = CRM_Contribute_BAO_ContributionPage::create($params); - $ufJoinParams = array( - 'is_organization' => array( + $ufJoinParams = [ + 'is_organization' => [ 'module' => 'on_behalf', 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $dao->id, - ), - 'honor_block_is_active' => array( + ], + 'honor_block_is_active' => [ 'module' => 'soft_credit', 'entity_table' => 'civicrm_contribution_page', 'entity_id' => $dao->id, - ), - ); + ], + ]; foreach ($ufJoinParams as $index => $ufJoinParam) { if (!empty($params[$index])) { @@ -406,7 +408,7 @@ public function postProcess() { $url = 'civicrm/admin/contribute'; $urlParams = 'reset=1'; CRM_Core_Session::setStatus(ts("'%1' information has been saved.", - array(1 => $this->getTitle()) + [1 => $this->getTitle()] ), ts('Saved'), 'success'); } diff --git a/CRM/Contribute/Form/ContributionPage/TabHeader.php b/CRM/Contribute/Form/ContributionPage/TabHeader.php index 8f9cad5e476c..0bf6b9373698 100644 --- a/CRM/Contribute/Form/ContributionPage/TabHeader.php +++ b/CRM/Contribute/Form/ContributionPage/TabHeader.php @@ -1,9 +1,9 @@ assign_by_ref('tabHeader', $tabs); CRM_Core_Resources::singleton() ->addScriptFile('civicrm', 'templates/CRM/common/TabHeader.js', 1, 'html-header') - ->addSetting(array( - 'tabSettings' => array( + ->addSetting([ + 'tabSettings' => [ 'active' => self::getCurrentTab($tabs), - ), - )); + ], + ]); return $tabs; } @@ -67,74 +68,46 @@ public static function process(&$form) { return NULL; } - $tabs = array( - 'settings' => array( + $default = [ + 'link' => NULL, + 'valid' => FALSE, + 'active' => FALSE, + 'current' => FALSE, + ]; + + $tabs = [ + 'settings' => [ 'title' => ts('Title'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'amount' => array( + ] + $default, + 'amount' => [ 'title' => ts('Amounts'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'membership' => array( + ] + $default, + 'membership' => [ 'title' => ts('Memberships'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'thankyou' => array( + ] + $default, + 'thankyou' => [ 'title' => ts('Receipt'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'friend' => array( + ] + $default, + 'friend' => [ 'title' => ts('Tell a Friend'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'custom' => array( + ] + $default, + 'custom' => [ 'title' => ts('Profiles'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'premium' => array( + ] + $default, + 'premium' => [ 'title' => ts('Premiums'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'widget' => array( + ] + $default, + 'widget' => [ 'title' => ts('Widgets'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - 'pcp' => array( + ] + $default, + 'pcp' => [ 'title' => ts('Personal Campaigns'), - 'link' => NULL, - 'valid' => FALSE, - 'active' => FALSE, - 'current' => FALSE, - ), - ); + ] + $default, + ]; $contribPageId = $form->getVar('_id'); - CRM_Utils_Hook::tabset('civicrm/admin/contribute', $tabs, array('contribution_page_id' => $contribPageId)); + // Call tabset hook to add/remove custom tabs + CRM_Utils_Hook::tabset('civicrm/admin/contribute', $tabs, ['contribution_page_id' => $contribPageId]); $fullName = $form->getVar('_name'); $className = CRM_Utils_String::getClassName($fullName); @@ -142,7 +115,7 @@ public static function process(&$form) { switch ($className) { case 'Contribute': $attributes = $form->getVar('_attributes'); - $class = strtolower(basename(CRM_Utils_Array::value('action', $attributes))); + $class = CRM_Utils_Request::retrieveComponent($attributes); break; case 'MembershipBlock': @@ -177,7 +150,7 @@ public static function process(&$form) { $tabs[$key]['active'] = $tabs[$key]['valid'] = TRUE; } //get all section info. - $contriPageInfo = CRM_Contribute_BAO_ContributionPage::getSectionInfo(array($contribPageId)); + $contriPageInfo = CRM_Contribute_BAO_ContributionPage::getSectionInfo([$contribPageId]); foreach ($contriPageInfo[$contribPageId] as $section => $info) { if (!$info) { diff --git a/CRM/Contribute/Form/ContributionPage/ThankYou.php b/CRM/Contribute/Form/ContributionPage/ThankYou.php index 9b90a9d7d385..d8a53f151a12 100644 --- a/CRM/Contribute/Form/ContributionPage/ThankYou.php +++ b/CRM/Contribute/Form/ContributionPage/ThankYou.php @@ -1,9 +1,9 @@ add('text', 'thankyou_title', ts('Thank-you Page Title'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'thankyou_title'), TRUE); - $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'thankyou_text') + array('class' => 'collapsed'); + $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'thankyou_text') + ['class' => 'collapsed']; $this->add('wysiwyg', 'thankyou_text', ts('Thank-you Message'), $attributes); $this->add('wysiwyg', 'thankyou_footer', ts('Thank-you Footer'), $attributes); - $this->addElement('checkbox', 'is_email_receipt', ts('Email Receipt to Contributor?'), NULL, array('onclick' => "showReceipt()")); + $this->addElement('checkbox', 'is_email_receipt', ts('Email Receipt to Contributor?'), NULL, ['onclick' => "showReceipt()"]); $this->add('text', 'receipt_from_name', ts('Receipt From Name'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'receipt_from_name')); $this->add('text', 'receipt_from_email', ts('Receipt From Email'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'receipt_from_email')); $this->add('textarea', 'receipt_text', ts('Receipt Message'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionPage', 'receipt_text')); @@ -70,7 +70,7 @@ public function buildQuickForm() { $this->addRule('bcc_receipt', ts('Please enter a valid list of comma delimited email addresses'), 'emailList'); parent::buildQuickForm(); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_ThankYou', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_ThankYou', 'formRule'], $this); } /** @@ -87,7 +87,7 @@ public function buildQuickForm() { * true if no errors, else array of errors */ public static function formRule($fields, $files, $options) { - $errors = array(); + $errors = []; // if is_email_receipt is set, the receipt message must be non-empty if (!empty($fields['is_email_receipt'])) { diff --git a/CRM/Contribute/Form/ContributionPage/Widget.php b/CRM/Contribute/Form/ContributionPage/Widget.php index 7824a8c5b38d..2a88bc06b5df 100644 --- a/CRM/Contribute/Form/ContributionPage/Widget.php +++ b/CRM/Contribute/Form/ContributionPage/Widget.php @@ -1,9 +1,9 @@ _fields = array( - 'title' => array( + $this->_fields = [ + 'title' => [ ts('Title'), 'text', FALSE, $title, - ), - 'url_logo' => array( + ], + 'url_logo' => [ ts('URL to Logo Image'), 'text', FALSE, NULL, - ), - 'button_title' => array( + ], + 'button_title' => [ ts('Button Title'), 'text', FALSE, ts('Contribute!'), - ), - ); + ], + ]; - $this->_colorFields = array( - 'color_title' => array( + $this->_colorFields = [ + 'color_title' => [ ts('Title Text Color'), - 'text', + 'color', FALSE, '#2786C2', - ), - 'color_bar' => array( + ], + 'color_bar' => [ ts('Progress Bar Color'), - 'text', + 'color', FALSE, '#2786C2', - ), - 'color_main_text' => array( + ], + 'color_main_text' => [ ts('Additional Text Color'), - 'text', + 'color', FALSE, '#FFFFFF', - ), - 'color_main' => array( + ], + 'color_main' => [ ts('Background Color'), - 'text', + 'color', FALSE, '#96C0E7', - ), - 'color_main_bg' => array( + ], + 'color_main_bg' => [ ts('Background Color Top Area'), - 'text', + 'color', FALSE, '#B7E2FF', - ), - 'color_bg' => array( + ], + 'color_bg' => [ ts('Border Color'), - 'text', + 'color', FALSE, '#96C0E7', - ), - 'color_about_link' => array( + ], + 'color_about_link' => [ ts('Button Text Color'), - 'text', + 'color', FALSE, '#556C82', - ), - 'color_button' => array( + ], + 'color_button' => [ ts('Button Background Color'), - 'text', + 'color', FALSE, '#FFFFFF', - ), - 'color_homepage_link' => array( + ], + 'color_homepage_link' => [ ts('Homepage Link Color'), - 'text', + 'color', FALSE, '#FFFFFF', - ), - ); + ], + ]; } /** * Set default values for the form. */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; // check if there is a widget already created if ($this->_widget) { CRM_Core_DAO::storeValues($this->_widget, $defaults); @@ -175,7 +175,7 @@ public function buildQuickForm() { 'is_active', ts('Enable Widget?'), NULL, - array('onclick' => "widgetBlock(this)") + ['onclick' => "widgetBlock(this)"] ); $this->add('wysiwyg', 'about', ts('About'), $attributes['about']); @@ -206,7 +206,7 @@ public function buildQuickForm() { ts('Save and Preview') ); parent::buildQuickForm(); - $this->addFormRule(array('CRM_Contribute_Form_ContributionPage_Widget', 'formRule'), $this); + $this->addFormRule(['CRM_Contribute_Form_ContributionPage_Widget', 'formRule'], $this); } /** @@ -222,7 +222,7 @@ public function buildQuickForm() { * mixed true or array of errors */ public static function formRule($params, $files, $self) { - $errors = array(); + $errors = []; if (!empty($params['is_active'])) { if (empty($params['title'])) { $errors['title'] = ts('Title is a required field.'); @@ -233,7 +233,7 @@ public static function formRule($params, $files, $self) { foreach ($params as $key => $val) { if (substr($key, 0, 6) == 'color_' && empty($params[$key])) { - $errors[$key] = ts('%1 is a required field.', array(1 => $self->_colorFields[$key][0])); + $errors[$key] = ts('%1 is a required field.', [1 => $self->_colorFields[$key][0]]); } } } diff --git a/CRM/Contribute/Form/ContributionRecur.php b/CRM/Contribute/Form/ContributionRecur.php new file mode 100644 index 000000000000..b333159eca83 --- /dev/null +++ b/CRM/Contribute/Form/ContributionRecur.php @@ -0,0 +1,233 @@ + 'id-source', 'file' => 'CRM/Contact/Form/Contact']] + * - template - use a field specific template to render this field + * @var array + */ + protected $entityFields = []; + + /** + * Details of the subscription (recurring contribution) to be altered. + * + * @var array + */ + protected $subscriptionDetails = []; + + /** + * Is the form being accessed by a front end user to update their own recurring. + * + * @var bool + */ + protected $selfService; + + /** + * Explicitly declare the entity api name. + */ + public function getDefaultEntity() { + return 'ContributionRecur'; + } + + /** + * Explicitly declare the form context. + */ + public function getDefaultContext() { + return 'create'; + } + + /** + * Get the entity id being edited. + * + * @return int|null + */ + public function getEntityId() { + return $this->contributionRecurID; + } + + /** + * Set variables up before form is built. + * + * @throws \CRM_Core_Exception + */ + public function preProcess() { + $this->setAction(CRM_Core_Action::UPDATE); + $this->_mid = CRM_Utils_Request::retrieve('mid', 'Integer', $this, FALSE); + $this->_crid = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); + $this->contributionRecurID = $this->_crid; + $this->_coid = CRM_Utils_Request::retrieve('coid', 'Integer', $this, FALSE); + $this->setSubscriptionDetails(); + $this->setPaymentProcessor(); + if ($this->getSubscriptionContactID()) { + $this->set('cid', $this->getSubscriptionContactID()); + } + } + + /** + * Set the payment processor object up. + * + * This is a function that needs to be better consolidated between the inheriting forms + * but this is good choice of function to call. + */ + protected function setPaymentProcessor() { + if ($this->_crid) { + $this->_paymentProcessor = CRM_Contribute_BAO_ContributionRecur::getPaymentProcessor($this->contributionRecurID); + if (!$this->_paymentProcessor) { + CRM_Core_Error::statusBounce(ts('There is no valid processor for this subscription so it cannot be updated')); + } + $this->_paymentProcessorObj = $this->_paymentProcessor['object']; + } + elseif ($this->_mid) { + $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_mid, 'membership', 'obj'); + $this->_paymentProcessor = $this->_paymentProcessorObj->getPaymentProcessor(); + } + } + + /** + * Set the subscription details on the form. + */ + protected function setSubscriptionDetails() { + if ($this->contributionRecurID) { + $this->subscriptionDetails = $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_crid); + } + elseif ($this->_coid) { + $this->subscriptionDetails = $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_coid, 'contribution'); + } + elseif ($this->_mid) { + $this->subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_mid, 'membership'); + } + // This is being set temporarily - we should eventually just use the getter fn. + $this->_subscriptionDetails = $this->subscriptionDetails; + } + + /** + * Get details for the recurring contribution being altered. + * + * @return array + */ + public function getSubscriptionDetails() { + return $this->subscriptionDetails; + } + + /** + * Get the contact ID for the subscription. + * + * @return int|false + */ + protected function getSubscriptionContactID() { + $sub = $this->getSubscriptionDetails(); + return isset($sub->contact_id) ? $sub->contact_id : FALSE; + } + + /** + * Is this being used by a front end user to update their own recurring. + * + * @return bool + */ + protected function isSelfService() { + if (!is_null($this->selfService)) { + return $this->selfService; + } + $this->selfService = FALSE; + if (!CRM_Core_Permission::check('edit contributions')) { + if ($this->_subscriptionDetails->contact_id != $this->getContactID()) { + CRM_Core_Error::statusBounce(ts('You do not have permission to cancel this recurring contribution.')); + } + $this->selfService = TRUE; + } + return $this->selfService; + } + +} diff --git a/CRM/Contribute/Form/ContributionView.php b/CRM/Contribute/Form/ContributionView.php index c7866a45bdb4..a9eb615488e0 100644 --- a/CRM/Contribute/Form/ContributionView.php +++ b/CRM/Contribute/Form/ContributionView.php @@ -1,9 +1,9 @@ get('id'); - $params = array('id' => $id); - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $params = ['id' => $id]; + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $this->assign('context', $context); $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params); @@ -94,7 +94,7 @@ public function preProcess() { if (!empty($values['contribution_recur_id'])) { $sql = "SELECT installments, frequency_interval, frequency_unit FROM civicrm_contribution_recur WHERE id = %1"; - $params = array(1 => array($values['contribution_recur_id'], 'Integer')); + $params = [1 => [$values['contribution_recur_id'], 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); if ($dao->fetch()) { $values['recur_installments'] = $dao->installments; @@ -103,7 +103,7 @@ public function preProcess() { } } - $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', $this, $id, 0, CRM_Utils_Array::value('financial_type_id', $values)); + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', NULL, $id, 0, CRM_Utils_Array::value('financial_type_id', $values)); CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $id); $premiumId = NULL; @@ -132,7 +132,7 @@ public function preProcess() { // show billing address location details, if exists if (!empty($values['address_id'])) { - $addressParams = array('id' => CRM_Utils_Array::value('address_id', $values)); + $addressParams = ['id' => CRM_Utils_Array::value('address_id', $values)]; $addressDetails = CRM_Core_BAO_Address::getValues($addressParams, FALSE, 'id'); $addressDetails = array_values($addressDetails); $values['billing_address'] = $addressDetails[0]['display']; @@ -150,10 +150,10 @@ public function preProcess() { $this->assign($name, $value); } - $lineItems = array(); + $lineItems = []; $displayLineItems = FALSE; if ($id) { - $lineItems = array(CRM_Price_BAO_LineItem::getLineItemsByContributionID(($id))); + $lineItems = [CRM_Price_BAO_LineItem::getLineItemsByContributionID(($id))]; $firstLineItem = reset($lineItems[0]); if (empty($firstLineItem['price_set_id'])) { // CRM-20297 All we care is that it's not QuickConfig, so no price set @@ -162,10 +162,10 @@ public function preProcess() { } else { try { - $priceSet = civicrm_api3('PriceSet', 'getsingle', array( + $priceSet = civicrm_api3('PriceSet', 'getsingle', [ 'id' => $firstLineItem['price_set_id'], 'return' => 'is_quick_config, id', - )); + ]); $displayLineItems = !$priceSet['is_quick_config']; } catch (CiviCRM_API3_Exception $e) { @@ -190,7 +190,7 @@ public function preProcess() { // assign values to the template $this->assign($values); $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); + $invoicing = CRM_Invoicing_Utils::isInvoicingEnabled(); $this->assign('invoicing', $invoicing); $this->assign('isDeferred', CRM_Utils_Array::value('deferred_revenue_enabled', $invoiceSettings)); if ($invoicing && isset($values['tax_amount'])) { @@ -215,7 +215,7 @@ public function preProcess() { $title = $displayName . ' - (' . CRM_Utils_Money::format($values['total_amount'], $values['currency']) . ' ' . ' - ' . $values['financial_type'] . ')'; - $recentOther = array(); + $recentOther = []; if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::UPDATE)) { $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution', "action=update&reset=1&id={$values['id']}&cid={$values['contact_id']}&context=home" @@ -235,7 +235,7 @@ public function preProcess() { $recentOther ); $contributionStatus = $status[$values['contribution_status_id']]; - if (in_array($contributionStatus, array('Partially paid', 'Pending refund')) + if (in_array($contributionStatus, ['Partially paid', 'Pending refund']) || ($contributionStatus == 'Pending' && $values['is_pay_later']) ) { if ($contributionStatus == 'Pending refund') { @@ -249,21 +249,44 @@ public function preProcess() { $this->assign('componentId', $id); $this->assign('component', 'contribution'); } + $this->assignPaymentInfoBlock($id); } /** * Build the form object. */ public function buildQuickForm() { - - $this->addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'cancel', 'name' => ts('Done'), 'spacing' => '         ', 'isDefault' => TRUE, - ), - )); + ], + ]); + } + + /** + * Assign the values to build the payment info block. + * + * @todo - this is a bit too much copy & paste from AbstractEditPayment + * (justifying on the basis it's 'pretty short' and in a different inheritance + * tree. I feel like traits are probably the longer term answer). + * + * @param int $id + * + * @return string + * Block title. + */ + protected function assignPaymentInfoBlock($id) { + // component is used in getPaymentInfo primarily to retrieve the contribution id, we + // already have that. + $paymentInfo = CRM_Contribute_BAO_Contribution::getPaymentInfo($id, 'contribution', TRUE); + $title = ts('View Payment'); + $this->assign('transaction', TRUE); + $this->assign('payments', $paymentInfo['transaction']); + $this->assign('paymentLinks', $paymentInfo['payment_links']); + return $title; } } diff --git a/CRM/Contribute/Form/ManagePremiums.php b/CRM/Contribute/Form/ManagePremiums.php index 2cd9d1a781df..bbb5543e20f0 100644 --- a/CRM/Contribute/Form/ManagePremiums.php +++ b/CRM/Contribute/Form/ManagePremiums.php @@ -1,9 +1,9 @@ _id) { - $params = array('id' => $this->_id); - CRM_Contribute_BAO_ManagePremiums::retrieve($params, $tempDefaults); - $imageUrl = (isset($tempDefaults['image'])) ? $tempDefaults['image'] : ""; + $params = ['id' => $this->_id]; + CRM_Contribute_BAO_Product::retrieve($params, $tempDefaults); if (isset($tempDefaults['image']) && isset($tempDefaults['thumbnail'])) { $defaults['imageUrl'] = $tempDefaults['image']; $defaults['thumbnailUrl'] = $tempDefaults['thumbnail']; @@ -92,10 +91,10 @@ public function buildQuickForm() { $this->applyFilter('__ALL__', 'trim'); $this->add('text', 'name', ts('Name'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Product', 'name'), TRUE); - $this->addRule('name', ts('A product with this name already exists. Please select another name.'), 'objectExists', array( - 'CRM_Contribute_DAO_Product', - $this->_id, - )); + $this->addRule('name', ts('A product with this name already exists. Please select another name.'), 'objectExists', [ + 'CRM_Contribute_DAO_Product', + $this->_id, + ]); $this->add('text', 'sku', ts('SKU'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Product', 'sku')); $this->add('textarea', 'description', ts('Description'), 'rows=3, cols=60'); @@ -124,25 +123,25 @@ public function buildQuickForm() { $this->add('textarea', 'options', ts('Options'), 'rows=3, cols=60'); - $this->add('select', 'period_type', ts('Period Type'), array( - '' => '- select -', - 'rolling' => 'Rolling', - 'fixed' => 'Fixed', - )); + $this->add('select', 'period_type', ts('Period Type'), [ + '' => '- select -', + 'rolling' => 'Rolling', + 'fixed' => 'Fixed', + ]); $this->add('text', 'fixed_period_start_day', ts('Fixed Period Start Day'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Product', 'fixed_period_start_day')); - $this->add('Select', 'duration_unit', ts('Duration Unit'), array('' => '- select period -') + CRM_Core_SelectValues::getPremiumUnits()); + $this->add('Select', 'duration_unit', ts('Duration Unit'), ['' => '- select period -'] + CRM_Core_SelectValues::getPremiumUnits()); $this->add('text', 'duration_interval', ts('Duration'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Product', 'duration_interval')); - $this->add('Select', 'frequency_unit', ts('Frequency Unit'), array('' => '- select period -') + CRM_Core_SelectValues::getPremiumUnits()); + $this->add('Select', 'frequency_unit', ts('Frequency Unit'), ['' => '- select period -'] + CRM_Core_SelectValues::getPremiumUnits()); $this->add('text', 'frequency_interval', ts('Frequency'), CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Product', 'frequency_interval')); //Financial Type CRM-11106 $financialType = CRM_Contribute_PseudoConstant::financialType(); - $premiumFinancialType = array(); + $premiumFinancialType = []; CRM_Core_PseudoConstant::populate( $premiumFinancialType, 'CRM_Financial_DAO_EntityFinancialAccount', @@ -152,7 +151,7 @@ public function buildQuickForm() { 'account_relationship = 8' ); - $costFinancialType = array(); + $costFinancialType = []; CRM_Core_PseudoConstant::populate( $costFinancialType, 'CRM_Financial_DAO_EntityFinancialAccount', @@ -174,25 +173,24 @@ public function buildQuickForm() { 'select', 'financial_type_id', ts('Financial Type'), - array('' => ts('- select -')) + $financialType + ['' => ts('- select -')] + $financialType ); $this->add('checkbox', 'is_active', ts('Enabled?')); - $this->addFormRule(array('CRM_Contribute_Form_ManagePremiums', 'formRule')); - - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addFormRule(['CRM_Contribute_Form_ManagePremiums', 'formRule']); + + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); $this->assign('productId', $this->_id); } @@ -201,35 +199,34 @@ public function buildQuickForm() { * * @param array $params * (ref.) an assoc array of name/value pairs. - * * @param $files * * @return bool|array * mixed true or array of errors */ public static function formRule($params, $files) { - if (isset($params['imageOption'])) { - if ($params['imageOption'] == 'thumbnail') { - if (!$params['imageUrl']) { - $errors['imageUrl'] = ts('Image URL is Required'); - } - if (!$params['thumbnailUrl']) { - $errors['thumbnailUrl'] = ts('Thumbnail URL is Required'); - } + + // If choosing to upload an image, then an image must be provided + if (CRM_Utils_Array::value('imageOption', $params) == 'image' + && empty($files['uploadFile']['name']) + ) { + $errors['uploadFile'] = ts('A file must be selected'); + } + + // If choosing to use image URLs, then both URLs must be present + if (CRM_Utils_Array::value('imageOption', $params) == 'thumbnail') { + if (!$params['imageUrl']) { + $errors['imageUrl'] = ts('Image URL is Required'); + } + if (!$params['thumbnailUrl']) { + $errors['thumbnailUrl'] = ts('Thumbnail URL is Required'); } } + // CRM-13231 financial type required if product has cost if (!empty($params['cost']) && empty($params['financial_type_id'])) { $errors['financial_type_id'] = ts('Financial Type is required for product having cost.'); } - $fileLocation = $files['uploadFile']['tmp_name']; - if ($fileLocation != "") { - list($width, $height) = getimagesize($fileLocation); - - if (($width < 80 || $width > 500) || ($height < 80 || $height > 500)) { - //$errors ['uploadFile'] = "Please Enter files with dimensions between 80 x 80 and 500 x 500," . " Dimensions of this file is ".$width."X".$height; - } - } if (!$params['period_type']) { if ($params['fixed_period_start_day'] || $params['duration_unit'] || $params['duration_interval'] || @@ -266,133 +263,112 @@ public static function formRule($params, $files) { * Process the form submission. */ public function postProcess() { - + // If previewing, don't do any post-processing if ($this->_action & CRM_Core_Action::PREVIEW) { return; } + // If deleting, then only delete and skip the rest of the post-processing if ($this->_action & CRM_Core_Action::DELETE) { - CRM_Contribute_BAO_ManagePremiums::del($this->_id); - CRM_Core_Session::setStatus(ts('Selected Premium Product type has been deleted.'), ts('Deleted'), 'info'); - } - else { - $params = $this->controller->exportValues($this->_name); - $imageFile = CRM_Utils_Array::value('uploadFile', $params); - $imageFile = $imageFile['name']; - - $config = CRM_Core_Config::singleton(); - - $ids = array(); - $error = FALSE; - // store the submitted values in an array - - // FIX ME - if (CRM_Utils_Array::value('imageOption', $params, FALSE)) { - $value = CRM_Utils_Array::value('imageOption', $params, FALSE); - if ($value == 'image') { - - // to check wether GD is installed or not - $gdSupport = CRM_Utils_System::getModuleSetting('gd', 'GD Support'); - if ($gdSupport) { - if ($imageFile) { - $error = FALSE; - $params['image'] = $this->_resizeImage($imageFile, "_full", 200, 200); - $params['thumbnail'] = $this->_resizeImage($imageFile, "_thumb", 50, 50); - } - } - else { - $error = TRUE; - $params['image'] = $config->resourceBase . 'i/contribute/default_premium.jpg'; - $params['thumbnail'] = $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; - } - } - elseif ($value == 'thumbnail') { - $params['image'] = $params['imageUrl']; - $params['thumbnail'] = $params['thumbnailUrl']; - } - elseif ($value == 'default_image') { - $url = parse_url($config->userFrameworkBaseURL); - $params['image'] = $config->resourceBase . 'i/contribute/default_premium.jpg'; - $params['thumbnail'] = $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; - } - else { - $params['image'] = ""; - $params['thumbnail'] = ""; - } + try { + CRM_Contribute_BAO_Product::del($this->_id); } - - if ($this->_action & CRM_Core_Action::UPDATE) { - $ids['premium'] = $this->_id; + catch (CRM_Core_Exception $e) { + $message = ts("This Premium is linked to an Online Contribution page. Please remove it before deleting this Premium.", [1 => CRM_Utils_System::url('civicrm/admin/contribute', 'reset=1')]); + CRM_Core_Session::setStatus($message, ts('Cannot delete Premium'), 'error'); + CRM_Core_Session::singleton()->pushUserContext(CRM_Utils_System::url('civicrm/admin/contribute/managePremiums', 'reset=1&action=browse')); + return; } + CRM_Core_Session::setStatus( + ts('Selected Premium Product type has been deleted.'), + ts('Deleted'), 'info'); + return; + } - // fix the money fields - foreach (array( - 'cost', - 'price', - 'min_contribution', - ) as $f) { - $params[$f] = CRM_Utils_Rule::cleanMoney($params[$f]); - } + $params = $this->controller->exportValues($this->_name); - $premium = CRM_Contribute_BAO_ManagePremiums::add($params, $ids); - if ($error) { - CRM_Core_Session::setStatus(ts('No thumbnail of your image was created because the GD image library is not currently compiled in your PHP installation. Product is currently configured to use default thumbnail image. If you have a local thumbnail image you can upload it separately and input the thumbnail URL by editing this premium.'), ts('Notice'), 'alert'); - } - else { - CRM_Core_Session::setStatus(ts("The Premium '%1' has been saved.", array(1 => $premium->name)), ts('Saved'), 'success'); - } + // Clean the the money fields + $moneyFields = ['cost', 'price', 'min_contribution']; + foreach ($moneyFields as $field) { + $params[$field] = CRM_Utils_Rule::cleanMoney($params[$field]); + } + + // If we're updating, we need to pass in the premium product Id + if ($this->_action & CRM_Core_Action::UPDATE) { + $params['id'] = $this->_id; } + + $this->_processImages($params); + + // Save the premium product to database + $premium = CRM_Contribute_BAO_Product::create($params); + + CRM_Core_Session::setStatus( + ts("The Premium '%1' has been saved.", [1 => $premium->name]), + ts('Saved'), 'success'); } /** - * Resize a premium image to a different size. + * Look at $params to find form info about images. Manipulate images if + * necessary. Then alter $params to point to the newly manipulated images. * - * - * @param string $filename - * @param string $resizedName - * @param $width - * @param $height - * - * @return string - * Path to image + * @param array $params */ - private function _resizeImage($filename, $resizedName, $width, $height) { - // figure out the new filename - $pathParts = pathinfo($filename); - $newFilename = $pathParts['dirname'] . "/" . $pathParts['filename'] . $resizedName . "." . $pathParts['extension']; - - // get image about original image - $imageInfo = getimagesize($filename); - $widthOrig = $imageInfo[0]; - $heightOrig = $imageInfo[1]; - $image = imagecreatetruecolor($width, $height); - if ($imageInfo['mime'] == 'image/gif') { - $source = imagecreatefromgif($filename); + protected function _processImages(&$params) { + $defaults = [ + 'imageOption' => 'noImage', + 'uploadFile' => ['name' => ''], + 'image' => '', + 'thumbnail' => '', + 'imageUrl' => '', + 'thumbnailUrl' => '', + ]; + $params = array_merge($defaults, $params); + + // User is uploading an image + if ($params['imageOption'] == 'image') { + $imageFile = $params['uploadFile']['name']; + try { + $params['image'] = CRM_Utils_File::resizeImage($imageFile, 200, 200, "_full"); + $params['thumbnail'] = CRM_Utils_File::resizeImage($imageFile, 50, 50, "_thumb"); + } + catch (CRM_Core_Exception $e) { + $params['image'] = self::_defaultImage(); + $params['thumbnail'] = self::_defaultThumbnail(); + $msg = ts('The product has been configured to use a default image.'); + CRM_Core_Session::setStatus($e->getMessage() . " $msg", ts('Notice'), 'alert'); + } } - elseif ($imageInfo['mime'] == 'image/png') { - $source = imagecreatefrompng($filename); + + // User is specifying existing URLs for the images + elseif ($params['imageOption'] == 'thumbnail') { + $params['image'] = $params['imageUrl']; + $params['thumbnail'] = $params['thumbnailUrl']; } - else { - $source = imagecreatefromjpeg($filename); + + // User wants a default image + elseif ($params['imageOption'] == 'default_image') { + $params['image'] = self::_defaultImage(); + $params['thumbnail'] = self::_defaultThumbnail(); } + } + + /** + * Returns the path to the default premium image + * @return string + */ + protected static function _defaultImage() { + $config = CRM_Core_Config::singleton(); + return $config->resourceBase . 'i/contribute/default_premium.jpg'; + } - // resize - imagecopyresized($image, $source, 0, 0, 0, 0, $width, $height, $widthOrig, $heightOrig); - - // save the resized image - $fp = fopen($newFilename, 'w+'); - ob_start(); - imagejpeg($image); - $image_buffer = ob_get_contents(); - ob_end_clean(); - imagedestroy($image); - fwrite($fp, $image_buffer); - rewind($fp); - fclose($fp); - - // return the URL to link to + /** + * Returns the path to the default premium thumbnail + * @return string + */ + protected static function _defaultThumbnail() { $config = CRM_Core_Config::singleton(); - return $config->imageUploadURL . basename($newFilename); + return $config->resourceBase . 'i/contribute/default_premium_thumb.jpg'; } } diff --git a/CRM/Contribute/Form/Search.php b/CRM/Contribute/Form/Search.php index 14c3ace64a77..6923169fe6b9 100644 --- a/CRM/Contribute/Form/Search.php +++ b/CRM/Contribute/Form/Search.php @@ -1,9 +1,9 @@ set('searchFormName', 'Search'); @@ -72,29 +83,10 @@ public function preProcess() { $this->_actionButtonName = $this->getButtonName('next', 'action'); $this->_done = FALSE; - // @todo - is this an error - $this->_defaults is used. - $this->defaults = array(); - - /* - * we allow the controller to set force/reset externally, useful when we are being - * driven by the wizard framework - */ - $this->_reset = CRM_Utils_Request::retrieve('reset', 'Boolean'); - $this->_force = CRM_Utils_Request::retrieve('force', 'Boolean', $this, FALSE); - $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, FALSE, 'search'); + $this->loadStandardSearchOptionsFromUrl(); - $this->assign("context", $this->_context); - - // get user submitted values - // get it from controller only if form has been submitted, else preProcess has set this - if (!empty($_POST)) { - $this->_formValues = $this->controller->exportValues($this->_name); - } - else { - $this->_formValues = $this->get('formValues'); - } + $this->_formValues = $this->getFormValues(); //membership ID $memberShipId = CRM_Utils_Request::retrieve('memberId', 'Positive', $this); @@ -107,8 +99,7 @@ public function preProcess() { } if ($this->_force) { - $this->postProcess(); - $this->set('force', 0); + $this->handleForcedSearch(); } $sortID = NULL; @@ -153,41 +144,48 @@ public function preProcess() { * Set defaults. * * @return array + * @throws \Exception */ public function setDefaultValues() { - if (empty($this->_defaults['contribution_status'])) { - $this->_defaults['contribution_status'][1] = 1; + $lowReceiveDate = CRM_Utils_Request::retrieve('start', 'Timestamp'); + if (!empty($lowReceiveDate)) { + $this->_formValues['receive_date_low'] = date('Y-m-d H:i:s', strtotime($lowReceiveDate)); + CRM_Core_Error::deprecatedFunctionWarning('pass receive_date_low not start'); + } + $highReceiveDate = CRM_Utils_Request::retrieve('end', 'Timestamp'); + if (!empty($highReceiveDate)) { + $this->_formValues['receive_date_high'] = date('Y-m-d H:i:s', strtotime($highReceiveDate)); + CRM_Core_Error::deprecatedFunctionWarning('pass receive_date_high not end'); + } + $this->_defaults = parent::setDefaultValues(); + + $this->_defaults = array_merge($this->getEntityDefaults('ContributionRecur'), $this->_defaults); + + if (empty($this->_defaults['contribution_status_id']) && !$this->_force) { + // In force mode only parameters from the url will be used. When visible/ explicit this is a useful default. + $this->_defaults['contribution_status_id'][1] = CRM_Core_PseudoConstant::getKey( + 'CRM_Contribute_BAO_Contribution', + 'contribution_status_id', + 'Completed' + ); } return $this->_defaults; } /** * Build the form object. + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public function buildQuickForm() { - parent::buildQuickForm(); - $this->addSortNameField(); - - $this->_group = CRM_Core_PseudoConstant::nestedGroup(); - - // multiselect for groups - if ($this->_group) { - $this->add('select', 'group', ts('Groups'), $this->_group, FALSE, - array('id' => 'group', 'multiple' => 'multiple', 'class' => 'crm-select2') - ); - } - - // multiselect for tags - $contactTags = CRM_Core_BAO_Tag::getTags(); + if ($this->isFormInViewOrEditMode()) { + parent::buildQuickForm(); + $this->addContactSearchFields(); - if ($contactTags) { - $this->add('select', 'contact_tags', ts('Tags'), $contactTags, FALSE, - array('id' => 'contact_tags', 'multiple' => 'multiple', 'class' => 'crm-select2') - ); + CRM_Contribute_BAO_Query::buildSearchForm($this); } - CRM_Contribute_BAO_Query::buildSearchForm($this); - $rows = $this->get('rows'); if (is_array($rows)) { if (!$this->_single) { @@ -197,11 +195,11 @@ public function buildQuickForm() { $permission = CRM_Core_Permission::getPermission(); $queryParams = $this->get('queryParams'); - $softCreditFiltering = FALSE; + $taskParams['softCreditFiltering'] = FALSE; if (!empty($queryParams)) { - $softCreditFiltering = CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled($queryParams); + $taskParams['softCreditFiltering'] = CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled($queryParams); } - $tasks = CRM_Contribute_Task::permissionedTaskTitles($permission, $softCreditFiltering); + $tasks = CRM_Contribute_Task::permissionedTaskTitles($permission, $taskParams); $this->addTaskMenu($tasks); } @@ -229,6 +227,36 @@ protected function getSortNameLabelWithOutEmail() { return ts('Contributor Name'); } + /** + * Get the label for the tag field. + * + * We do this in a function so the 'ts' wraps the whole string to allow + * better translation. + * + * @return string + */ + protected function getTagLabel() { + return ts('Contributor Tag(s)'); + } + + /** + * Get the label for the group field. + * + * @return string + */ + protected function getGroupLabel() { + return ts('Contributor Group(s)'); + } + + /** + * Get the label for the group field. + * + * @return string + */ + protected function getContactTypeLabel() { + return ts('Contributor Contact Type'); + } + /** * The post processing of the form gets done here. * @@ -248,10 +276,7 @@ public function postProcess() { $this->_done = TRUE; - if (!empty($_POST)) { - $this->_formValues = $this->controller->exportValues($this->_name); - } - + $this->setFormValues(); $this->fixFormValues(); // We don't show test records in summaries or dashboards @@ -259,10 +284,10 @@ public function postProcess() { $this->_formValues["contribution_test"] = 0; } - foreach (array( - 'contribution_amount_low', - 'contribution_amount_high', - ) as $f) { + foreach ([ + 'contribution_amount_low', + 'contribution_amount_high', + ] as $f) { if (isset($this->_formValues[$f])) { $this->_formValues[$f] = CRM_Utils_Rule::cleanMoney($this->_formValues[$f]); } @@ -270,18 +295,17 @@ public function postProcess() { $config = CRM_Core_Config::singleton(); if (!empty($_POST)) { - $specialParams = array( + $specialParams = [ 'financial_type_id', 'contribution_soft_credit_type_id', 'contribution_status_id', - 'contribution_source', 'contribution_trxn_id', 'contribution_page_id', 'contribution_product_id', 'invoice_id', 'payment_instrument_id', 'contribution_batch_id', - ); + ]; CRM_Contact_BAO_Query::processSpecialFormValue($this->_formValues, $specialParams); $tags = CRM_Utils_Array::value('contact_tags', $this->_formValues); @@ -309,7 +333,6 @@ public function postProcess() { $this->_formValues['group'][$groupID] = 1; } } - } CRM_Core_BAO_CustomValue::fixCustomFieldValue($this->_formValues); @@ -366,9 +389,7 @@ public function postProcess() { if ($this->_context == 'user') { $query->setSkipPermission(TRUE); } - $summary = &$query->summaryContribution($this->_context); - $this->set('summary', $summary); - $this->assign('contributionSummary', $summary); + $controller->run(); } @@ -384,8 +405,18 @@ public function fixFormValues() { $status = CRM_Utils_Request::retrieve('status', 'String'); if ($status) { - $this->_formValues['contribution_status_id'] = array($status => 1); - $this->_defaults['contribution_status_id'] = array($status => 1); + $this->_formValues['contribution_status_id'] = [$status => 1]; + $this->_defaults['contribution_status_id'] = [$status => 1]; + } + + $pcpid = (array) CRM_Utils_Request::retrieve('pcpid', 'String', $this); + if ($pcpid) { + // Add new pcpid to the tail of the array... + foreach ($pcpid as $pcpIdList) { + $this->_formValues['contribution_pcp_made_through_id'][] = $pcpIdList; + } + // and avoid any duplicate + $this->_formValues['contribution_pcp_made_through_id'] = array_unique($this->_formValues['contribution_pcp_made_through_id']); } $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this); @@ -404,25 +435,6 @@ public function fixFormValues() { } } - $lowDate = CRM_Utils_Request::retrieve('start', 'Timestamp'); - if ($lowDate) { - $lowDate = CRM_Utils_Type::escape($lowDate, 'Timestamp'); - $date = CRM_Utils_Date::setDateDefaults($lowDate); - $this->_formValues['contribution_date_low'] = $this->_defaults['contribution_date_low'] = $date[0]; - } - - $highDate = CRM_Utils_Request::retrieve('end', 'Timestamp'); - if ($highDate) { - $highDate = CRM_Utils_Type::escape($highDate, 'Timestamp'); - $date = CRM_Utils_Date::setDateDefaults($highDate); - $this->_formValues['contribution_date_high'] = $this->_defaults['contribution_date_high'] = $date[0]; - } - - if ($highDate || $lowDate) { - //set the Choose Date Range value - $this->_formValues['contribution_date_relative'] = 0; - } - $this->_limit = CRM_Utils_Request::retrieve('limit', 'Positive', $this ); @@ -458,4 +470,14 @@ public function getTitle() { return ts('Find Contributions'); } + /** + * Set the metadata for the form. + * + * @throws \CiviCRM_API3_Exception + */ + protected function setSearchMetadata() { + $this->addSearchFieldMetadata(['Contribution' => CRM_Contribute_BAO_Query::getSearchFieldMetadata()]); + $this->addSearchFieldMetadata(['ContributionRecur' => CRM_Contribute_BAO_ContributionRecur::getContributionRecurSearchFieldMetadata()]); + } + } diff --git a/CRM/Contribute/Form/SearchContribution.php b/CRM/Contribute/Form/SearchContribution.php index 781ac5fa798d..1b3130469550 100644 --- a/CRM/Contribute/Form/SearchContribution.php +++ b/CRM/Contribute/Form/SearchContribution.php @@ -1,9 +1,9 @@ addButtons(array( - array( + $this->addButtons([ + [ 'type' => 'refresh', 'name' => ts('Search'), 'isDefault' => TRUE, - ), - )); + ], + ]); } public function postProcess() { @@ -62,7 +62,7 @@ public function postProcess() { $parent = $this->controller->getParent(); $parent->set('searchResult', 1); if (!empty($params)) { - $fields = array('title', 'financial_type_id', 'campaign_id'); + $fields = ['title', 'financial_type_id', 'campaign_id']; foreach ($fields as $field) { if (isset($params[$field]) && !CRM_Utils_System::isNull($params[$field]) diff --git a/CRM/Contribute/Form/SoftCredit.php b/CRM/Contribute/Form/SoftCredit.php index 21b5dcf292db..431cb24dce16 100644 --- a/CRM/Contribute/Form/SoftCredit.php +++ b/CRM/Contribute/Form/SoftCredit.php @@ -1,9 +1,9 @@ id = $form->_id; - $contriDAO->find(TRUE); - if ($contriDAO->contribution_page_id) { - $ufJoinParams = array( - 'module' => 'soft_credit', - 'entity_table' => 'civicrm_contribution_page', - 'entity_id' => $contriDAO->contribution_page_id, - ); - $profileId = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); - - //check if any honree profile is enabled if yes then assign its profile type to $_honoreeProfileType - // which will be used to constraint soft-credit contact type in formRule, CRM-13981 - if (!empty($profileId[0]) && !empty($profileId[2])) { - $form->_honoreeProfileType = CRM_Core_BAO_UFGroup::getContactType($profileId[0]); - } - } - } - /** * Function used to build form element for soft credit block. * @@ -76,7 +51,7 @@ public static function buildQuickForm(&$form) { if ($ufJoinDAO->find(TRUE)) { $jsonData = CRM_Contribute_BAO_ContributionPage::formatModuleData($ufJoinDAO->module_data, TRUE, 'soft_credit'); if ($jsonData) { - foreach (array('honor_block_title', 'honor_block_text') as $name) { + foreach (['honor_block_title', 'honor_block_text'] as $name) { $form->assign($name, $jsonData[$name]); } @@ -93,7 +68,7 @@ public static function buildQuickForm(&$form) { } // by default generate 10 blocks - $item_count = 11; + $item_count = $form->_softCreditItemCount; $showSoftCreditRow = 2; if ($form->getAction() & CRM_Core_Action::UPDATE) { @@ -121,15 +96,15 @@ public static function buildQuickForm(&$form) { } for ($rowNumber = 1; $rowNumber <= $item_count; $rowNumber++) { - $form->addEntityRef("soft_credit_contact_id[{$rowNumber}]", ts('Contact'), array('create' => TRUE)); + $form->addEntityRef("soft_credit_contact_id[{$rowNumber}]", ts('Contact'), ['create' => TRUE]); $form->addMoney("soft_credit_amount[{$rowNumber}]", ts('Amount'), FALSE, NULL, FALSE); - $form->addSelect("soft_credit_type[{$rowNumber}]", array( - 'entity' => 'contribution_soft', - 'field' => 'soft_credit_type_id', - 'label' => ts('Type'), - )); + $form->addSelect("soft_credit_type[{$rowNumber}]", [ + 'entity' => 'contribution_soft', + 'field' => 'soft_credit_type_id', + 'label' => ts('Type'), + ]); if (!empty($form->_softCreditInfo['soft_credit'][$rowNumber]['soft_credit_id'])) { $form->add('hidden', "soft_credit_id[{$rowNumber}]", $form->_softCreditInfo['soft_credit'][$rowNumber]['soft_credit_id']); @@ -142,7 +117,7 @@ public static function buildQuickForm(&$form) { $form->assign('rowCount', $item_count); $form->addElement('hidden', 'sct_default_id', CRM_Core_OptionGroup::getDefaultValue("soft_credit_type"), - array('id' => 'sct_default_id') + ['id' => 'sct_default_id'] ); } @@ -160,7 +135,7 @@ public static function addPCPFields(&$form, $suffix = '') { if (!CRM_Utils_Array::crmIsEmptyArray($siteHasPCPs)) { $form->assign('siteHasPCPs', 1); // Fixme: Not a true entityRef field. Relies on PCP.js.tpl - $form->add('text', "pcp_made_through_id$suffix", ts('Credit to a Personal Campaign Page'), array('class' => 'twenty', 'placeholder' => ts('- select -'))); + $form->add('text', "pcp_made_through_id$suffix", ts('Credit to a Personal Campaign Page'), ['class' => 'twenty', 'placeholder' => ts('- select -')]); // stores the label $form->add('hidden', "pcp_made_through$suffix"); $form->addElement('checkbox', "pcp_display_in_roll$suffix", ts('Display in Honor Roll?'), NULL); @@ -216,7 +191,7 @@ public static function setDefaultValues(&$defaults, &$form) { * Array of errors */ public static function formRule($fields, $errors, $self) { - $errors = array(); + $errors = []; // if honor roll fields are populated but no PCP is selected if (empty($fields['pcp_made_through_id'])) { @@ -242,10 +217,6 @@ public static function formRule($fields, $errors, $self) { if (empty($fields['soft_credit_amount'][$key])) { $errors["soft_credit_amount[$key]"] = ts('Please enter the soft credit amount.'); } - $contactType = CRM_Contact_BAO_Contact::getContactType($fields['soft_credit_contact_id'][$key]); - if ($self->_honoreeProfileType && $self->_honoreeProfileType != $contactType) { - $errors["soft_credit_contact_id[$key]"] = ts('Please choose a contact of type %1', array(1 => $self->_honoreeProfileType)); - } } } } diff --git a/CRM/Contribute/Form/Task.php b/CRM/Contribute/Form/Task.php index 3f1a4e50f8a2..bd509799230a 100644 --- a/CRM/Contribute/Form/Task.php +++ b/CRM/Contribute/Form/Task.php @@ -1,9 +1,9 @@ _contributionIds = array(); + public static function preProcessCommon(&$form) { + $form->_contributionIds = []; $values = $form->controller->exportValues($form->get('searchFormName')); @@ -105,7 +77,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $contributeTasks = CRM_Contribute_Task::tasks(); $form->assign('taskName', CRM_Utils_Array::value($form->_task, $contributeTasks)); - $ids = array(); + $ids = []; if (isset($values['radio_ts']) && $values['radio_ts'] == 'ts_sel') { foreach ($values as $name => $value) { if (substr($name, 0, CRM_Core_Form::CB_PREFIX_LEN) == CRM_Core_Form::CB_PREFIX) { @@ -116,27 +88,29 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { else { $queryParams = $form->get('queryParams'); $isTest = FALSE; - foreach ($queryParams as $fields) { - if ($fields[0] == 'contribution_test') { - $isTest = TRUE; - break; + if (is_array($queryParams)) { + foreach ($queryParams as $fields) { + if ($fields[0] == 'contribution_test') { + $isTest = TRUE; + break; + } } } if (!$isTest) { - $queryParams[] = array( + $queryParams[] = [ 'contribution_test', '=', 0, 0, 0, - ); + ]; } - $returnProperties = array('contribution_id' => 1); + $returnProperties = ['contribution_id' => 1]; $sortOrder = $sortCol = NULL; if ($form->get(CRM_Utils_Sort::SORT_ORDER)) { $sortOrder = $form->get(CRM_Utils_Sort::SORT_ORDER); //Include sort column in select clause. - $sortCol = trim(str_replace(array('`', 'asc', 'desc'), '', $sortOrder)); + $sortCol = trim(str_replace(['`', 'asc', 'desc'], '', $sortOrder)); $returnProperties[$sortCol] = 1; } @@ -147,7 +121,7 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { // @todo the function CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled should handle this // can we remove? if not why not? if ($form->_includesSoftCredits) { - $contactIds = $contributionContactIds = array(); + $contactIds = $contributionContactIds = []; $query->_rowCountClause = " count(civicrm_contribution.id)"; $query->_groupByComponentClause = " GROUP BY contribution_search_scredit_combined.id, contribution_search_scredit_combined.contact_id, contribution_search_scredit_combined.scredit_id "; } @@ -163,7 +137,6 @@ public static function preProcessCommon(&$form, $useTable = FALSE) { $contributionContactIds["{$result->contact_id}-{$result->contribution_id}"] = $result->contribution_id; } } - $result->free(); $form->assign('totalSelectedContributions', $form->get('rowCount')); } @@ -215,7 +188,7 @@ public function setContributionIds($contributionIds) { */ public function setContactIDs() { if (!$this->_includesSoftCredits) { - $this->_contactIds = &CRM_Core_DAO::getContactIDsFromComponent( + $this->_contactIds = CRM_Core_DAO::getContactIDsFromComponent( $this->_contributionIds, 'civicrm_contribution' ); @@ -234,18 +207,17 @@ public function setContactIDs() { * @param bool $submitOnce */ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back', $submitOnce = FALSE) { - $this->addButtons(array( - array( - 'type' => $nextType, - 'name' => $title, - 'isDefault' => TRUE, - ), - array( - 'type' => $backType, - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => $nextType, + 'name' => $title, + 'isDefault' => TRUE, + ], + [ + 'type' => $backType, + 'name' => ts('Cancel'), + ], + ]); } } diff --git a/CRM/Contribute/Form/Task/Batch.php b/CRM/Contribute/Form/Task/Batch.php index 3964d6cbdcb1..b2f32ee2549c 100644 --- a/CRM/Contribute/Form/Task/Batch.php +++ b/CRM/Contribute/Form/Task/Batch.php @@ -1,9 +1,9 @@ ts('Name')), + $readOnlyFields = array_merge(['sort_name' => ts('Name')], CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'contact_autocomplete_options', TRUE, NULL, FALSE, 'name', TRUE @@ -89,12 +91,12 @@ public function buildQuickForm() { CRM_Utils_System::setTitle($this->_title); $this->addDefaultButtons(ts('Save')); - $this->_fields = array(); + $this->_fields = []; $this->_fields = CRM_Core_BAO_UFGroup::getFields($ufGroupId, FALSE, CRM_Core_Action::VIEW); // remove file type field and then limit fields $suppressFields = FALSE; - $removehtmlTypes = array('File', 'Autocomplete-Select'); + $removehtmlTypes = ['File']; foreach ($this->_fields as $name => $field) { if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) && in_array($this->_fields[$name]['html_type'], $removehtmlTypes) @@ -112,25 +114,24 @@ public function buildQuickForm() { $this->_fields = array_slice($this->_fields, 0, $this->_maxFields); - $this->addButtons(array( - array( - 'type' => 'submit', - 'name' => ts('Update Contribution(s)'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'submit', + 'name' => ts('Update Contribution(s)'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); $this->assign('profileTitle', $this->_title); $this->assign('componentIds', $this->_contributionIds); //load all campaigns. if (array_key_exists('contribution_campaign_id', $this->_fields)) { - $this->_componentCampaigns = array(); + $this->_componentCampaigns = []; CRM_Core_PseudoConstant::populate($this->_componentCampaigns, 'CRM_Contribute_DAO_Contribution', TRUE, 'campaign_id', 'id', @@ -141,14 +142,14 @@ public function buildQuickForm() { // It is possible to have fields that are required in CiviCRM not be required in the // profile. Overriding that here. Perhaps a better approach would be to // make them required in the schema & read that up through getFields functionality. - $requiredFields = array('receive_date'); + $requiredFields = ['receive_date']; //fix for CRM-2752 $customFields = CRM_Core_BAO_CustomField::getFields('Contribution'); foreach ($this->_contributionIds as $contributionId) { $typeId = CRM_Core_DAO::getFieldValue("CRM_Contribute_DAO_Contribution", $contributionId, 'financial_type_id'); foreach ($this->_fields as $name => $field) { - $entityColumnValue = array(); + $entityColumnValue = []; if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) { $customValue = CRM_Utils_Array::value($customFieldID, $customFields); if (!empty($customValue['extends_entity_column_value'])) { @@ -179,7 +180,7 @@ public function buildQuickForm() { $buttonName = $this->controller->getButtonName('submit'); if ($suppressFields && $buttonName != '_qf_Batch_next') { - CRM_Core_Session::setStatus(ts("File or Autocomplete-Select type field(s) in the selected profile are not supported for Update multiple contributions."), ts('Unsupported Field Type'), 'error'); + CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple contributions."), ts('Unsupported Field Type'), 'error'); } $this->addDefaultButtons(ts('Update Contributions')); @@ -193,7 +194,7 @@ public function setDefaultValues() { return; } - $defaults = array(); + $defaults = []; foreach ($this->_contributionIds as $contributionId) { CRM_Core_BAO_UFGroup::setProfileDefaults(NULL, $this->_fields, $defaults, FALSE, $contributionId, 'Contribute'); } @@ -206,6 +207,9 @@ public function setDefaultValues() { */ public function postProcess() { $params = $this->exportValues(); + // @todo extract submit functions & + // extend CRM_Event_Form_Task_BatchTest::testSubmit with a data provider to test + // handling of custom data, specifically checkbox fields. if (isset($params['field'])) { foreach ($params['field'] as $contributionID => $value) { @@ -214,9 +218,9 @@ public function postProcess() { $value['financial_type_id'] = $value['financial_type']; } - $value['options'] = array( + $value['options'] = [ 'reload' => 1, - ); + ]; $contribution = civicrm_api3('Contribution', 'create', $value); $contribution = $contribution['values'][$contributionID]; diff --git a/CRM/Contribute/Form/Task/Delete.php b/CRM/Contribute/Form/Task/Delete.php index 76b81892225c..0722cd9541a6 100644 --- a/CRM/Contribute/Form/Task/Delete.php +++ b/CRM/Contribute/Form/Task/Delete.php @@ -1,9 +1,9 @@ _contributionIds)) { - CRM_Core_Session::setStatus(ts('1 contribution could not be deleted.', array('plural' => '%count contributions could not be deleted.', 'count' => $count)), ts('Error'), 'error'); - $this->addButtons(array( - array( + CRM_Core_Session::setStatus(ts('1 contribution could not be deleted.', ['plural' => '%count contributions could not be deleted.', 'count' => $count]), ts('Error'), 'error'); + $this->addButtons([ + [ 'type' => 'back', 'name' => ts('Cancel'), - ), - ) - ); + ], + ]); } elseif ($count && !empty($this->_contributionIds)) { - CRM_Core_Session::setStatus(ts('1 contribution will not be deleted.', array('plural' => '%count contributions will not be deleted.', 'count' => $count)), ts('Warning'), 'warning'); + CRM_Core_Session::setStatus(ts('1 contribution will not be deleted.', ['plural' => '%count contributions will not be deleted.', 'count' => $count]), ts('Warning'), 'warning'); $this->addDefaultButtons(ts('Delete Contributions'), 'done'); } else { @@ -115,12 +114,12 @@ public function postProcess() { } if ($deleted) { - $msg = ts('%count contribution deleted.', array('plural' => '%count contributions deleted.', 'count' => $deleted)); + $msg = ts('%count contribution deleted.', ['plural' => '%count contributions deleted.', 'count' => $deleted]); CRM_Core_Session::setStatus($msg, ts('Removed'), 'success'); } if ($failed) { - CRM_Core_Session::setStatus(ts('1 could not be deleted.', array('plural' => '%count could not be deleted.', 'count' => $failed)), ts('Error'), 'error'); + CRM_Core_Session::setStatus(ts('1 could not be deleted.', ['plural' => '%count could not be deleted.', 'count' => $failed]), ts('Error'), 'error'); } } diff --git a/CRM/Contribute/Form/Task/Email.php b/CRM/Contribute/Form/Task/Email.php index 0156f14a410b..56cb5d471b49 100644 --- a/CRM/Contribute/Form/Task/Email.php +++ b/CRM/Contribute/Form/Task/Email.php @@ -1,9 +1,9 @@ _contributionIds = array($id); + $this->_contributionIds = [$id]; $this->_componentClause = " civicrm_contribution.id IN ( $id ) "; $this->_single = TRUE; $this->assign('totalSelectedContributions', 1); @@ -90,10 +94,10 @@ public function preProcess() { parent::preProcess(); } - // check that all the contribution ids have status Completed, Pending, Refunded. + // check that all the contribution ids have status Completed, Pending, Refunded, or Partially Paid. $this->_contributionStatusId = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); - $status = array('Completed', 'Pending', 'Refunded'); - $statusId = array(); + $status = ['Completed', 'Pending', 'Refunded', 'Partially paid']; + $statusId = []; foreach ($this->_contributionStatusId as $key => $value) { if (in_array($value, $status)) { $statusId[] = $key; @@ -103,7 +107,7 @@ public function preProcess() { $query = "SELECT count(*) FROM civicrm_contribution WHERE contribution_status_id NOT IN ($Id) AND {$this->_componentClause}"; $count = CRM_Core_DAO::singleValueQuery($query); if ($count != 0) { - CRM_Core_Error::statusBounce(ts('Please select only contributions with Completed, Pending, Refunded status.')); + CRM_Core_Error::statusBounce(ts('Please select only contributions with Completed, Pending, Refunded, or Partially Paid status.')); } // we have all the contribution ids, so now we get the contact ids @@ -117,18 +121,19 @@ public function preProcess() { } $url = CRM_Utils_System::url('civicrm/contribute/search', $urlParams); - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'url' => $url, 'title' => ts('Search Results'), - ), - ); + ], + ]; CRM_Utils_System::appendBreadCrumb($breadCrumb); $this->_selectedOutput = CRM_Utils_Request::retrieve('select', 'String', $this); $this->assign('selectedOutput', $this->_selectedOutput); + CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($this); if ($this->_selectedOutput == 'email') { CRM_Utils_System::setTitle(ts('Email Invoice')); } @@ -141,63 +146,38 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $session = CRM_Core_Session::singleton(); $this->preventAjaxSubmit(); if (CRM_Core_Permission::check('administer CiviCRM')) { $this->assign('isAdmin', 1); } - $contactID = $session->get('userID'); - $contactEmails = CRM_Core_BAO_Email::allEmails($contactID); - $emails = array(); - $fromDisplayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', - $contactID, 'display_name' - ); - foreach ($contactEmails as $emailId => $item) { - $email = $item['email']; - if ($email) { - $emails[$emailId] = '"' . $fromDisplayName . '" <' . $email . '> '; - } - if (isset($emails[$emailId])) { - $emails[$emailId] .= $item['locationType']; - if ($item['is_primary']) { - $emails[$emailId] .= ' ' . ts('(preferred)'); - } - $emails[$emailId] = htmlspecialchars($emails[$emailId]); - } - } - $fromEmailAddress = CRM_Core_OptionGroup::values('from_email_address'); - foreach ($fromEmailAddress as $key => $email) { - $fromEmailAddress[$key] = htmlspecialchars($fromEmailAddress[$key]); - } - $fromEmail = CRM_Utils_Array::crmArrayMerge($emails, $fromEmailAddress); - $this->add('select', 'from_email_address', ts('From Email Address'), array('' => '- select -') + $fromEmail); + + $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails, TRUE); if ($this->_selectedOutput != 'email') { $this->addElement('radio', 'output', NULL, ts('Email Invoice'), 'email_invoice'); $this->addElement('radio', 'output', NULL, ts('PDF Invoice'), 'pdf_invoice'); $this->addRule('output', ts('Selection required'), 'required'); - $this->addFormRule(array('CRM_Contribute_Form_Task_Invoice', 'formRule')); + $this->addFormRule(['CRM_Contribute_Form_Task_Invoice', 'formRule']); } else { $this->addRule('from_email_address', ts('From Email Address is required'), 'required'); } - $this->add('wysiwyg', 'email_comment', ts('If you would like to add personal message to email please add it here. (If sending to more then one receipient the same message will be sent to each contact.)'), array( + $this->add('wysiwyg', 'email_comment', ts('If you would like to add personal message to email please add it here. (If sending to more then one receipient the same message will be sent to each contact.)'), [ 'rows' => 2, 'cols' => 40, - )); - - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => $this->_selectedOutput == 'email' ? ts('Send Email') : ts('Process Invoice(s)'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + ]); + + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => $this->_selectedOutput == 'email' ? ts('Send Email') : ts('Process Invoice(s)'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -209,7 +189,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($values) { - $errors = array(); + $errors = []; if ($values['output'] == 'email_invoice' && empty($values['from_email_address'])) { $errors['from_email_address'] = ts("From Email Address is required"); @@ -238,7 +218,7 @@ public function postProcess() { */ public static function printPDF($contribIDs, &$params, $contactIds) { // get all the details needed to generate a invoice - $messageInvoice = array(); + $messageInvoice = []; $invoiceTemplate = CRM_Core_Smarty::singleton(); $invoiceElements = CRM_Contribute_Form_Task_PDF::getElements($contribIDs, $params, $contactIds); @@ -252,7 +232,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { $prefixValue = Civi::settings()->get('contribution_invoice_settings'); foreach ($invoiceElements['details'] as $contribID => $detail) { - $input = $ids = $objects = array(); + $input = $ids = $objects = []; if (in_array($detail['contact'], $invoiceElements['excludeContactIds'])) { continue; } @@ -281,17 +261,17 @@ public static function printPDF($contribIDs, &$params, $contactIds) { $objects['contribution']->receive_date = CRM_Utils_Date::isoToMysql($objects['contribution']->receive_date); - $addressParams = array('contact_id' => $contribution->contact_id); + $addressParams = ['contact_id' => $contribution->contact_id]; $addressDetails = CRM_Core_BAO_Address::getValues($addressParams); // to get billing address if present - $billingAddress = array(); + $billingAddress = []; foreach ($addressDetails as $address) { - if ((isset($address['is_billing']) && $address['is_billing'] == 1) && (isset($address['is_primary']) && $address['is_primary'] == 1) && $address['contact_id'] == $contribution->contact_id) { + if (($address['is_billing'] == 1) && ($address['is_primary'] == 1) && ($address['contact_id'] == $contribution->contact_id)) { $billingAddress[$address['contact_id']] = $address; break; } - elseif (($address['is_billing'] == 0 && $address['is_primary'] == 1) || (isset($address['is_billing']) && $address['is_billing'] == 1) && $address['contact_id'] == $contribution->contact_id) { + elseif (($address['is_billing'] == 0 && $address['is_primary'] == 1) || ($address['is_billing'] == 1) && ($address['contact_id'] == $contribution->contact_id)) { $billingAddress[$address['contact_id']] = $address; } } @@ -305,6 +285,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { if ($contribution->contribution_status_id == $refundedStatusId || $contribution->contribution_status_id == $cancelledStatusId) { if (is_null($contribution->creditnote_id)) { + CRM_Core_Error::deprecatedFunctionWarning('This it the wrong place to add a credit note id since the id is added when the status is changed in the Contribution::Create function- hopefully it is never hit'); $creditNoteId = CRM_Contribute_BAO_Contribution::createCreditNoteId(); CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contribution->id, 'creditnote_id', $creditNoteId); } @@ -312,25 +293,21 @@ public static function printPDF($contribIDs, &$params, $contactIds) { $creditNoteId = $contribution->creditnote_id; } } - $invoiceId = CRM_Utils_Array::value('invoice_prefix', $prefixValue) . "" . $contribution->id; + if (!$contribution->invoice_number) { + $contribution->invoice_number = CRM_Contribute_BAO_Contribution::getInvoiceNumber($contribution->id); + } //to obtain due date for PDF invoice $contributionReceiveDate = date('F j,Y', strtotime(date($input['receive_date']))); $invoiceDate = date("F j, Y"); - $dueDate = date('F j ,Y', strtotime($contributionReceiveDate . "+" . $prefixValue['due_date'] . "" . $prefixValue['due_date_period'])); + $dueDate = date('F j, Y', strtotime($contributionReceiveDate . "+" . $prefixValue['due_date'] . "" . $prefixValue['due_date_period'])); - if ($input['component'] == 'contribute') { - $lineItem = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contribID); - } - else { - $eid = $contribution->_relatedObjects['participant']->id; - $lineItem = CRM_Price_BAO_LineItem::getLineItems($eid, 'participant', NULL, TRUE, FALSE, TRUE); - } + $lineItem = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contribID); - $resultPayments = civicrm_api3('Payment', 'get', array( - 'sequential' => 1, - 'contribution_id' => $contribID, - )); + $resultPayments = civicrm_api3('Payment', 'get', [ + 'sequential' => 1, + 'contribution_id' => $contribID, + ]); $amountPaid = 0; foreach ($resultPayments['values'] as $singlePayment) { // Only count payments that have been (status =) completed. @@ -341,7 +318,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { $amountDue = ($input['amount'] - $amountPaid); // retrieving the subtotal and sum of same tax_rate - $dataArray = array(); + $dataArray = []; $subTotal = 0; foreach ($lineItem as $taxRate) { if (isset($dataArray[(string) $taxRate['tax_rate']])) { @@ -354,18 +331,18 @@ public static function printPDF($contribIDs, &$params, $contactIds) { } // to email the invoice - $mailDetails = array(); - $values = array(); + $mailDetails = []; + $values = []; if ($contribution->_component == 'event') { $daoName = 'CRM_Event_DAO_Event'; $pageId = $contribution->_relatedObjects['event']->id; - $mailElements = array( + $mailElements = [ 'title', 'confirm_from_name', 'confirm_from_email', 'cc_confirm', 'bcc_confirm', - ); + ]; CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements); $values['title'] = CRM_Utils_Array::value('title', $mailDetails[$contribution->_relatedObjects['event']->id]); $values['confirm_from_name'] = CRM_Utils_Array::value('confirm_from_name', $mailDetails[$contribution->_relatedObjects['event']->id]); @@ -378,13 +355,13 @@ public static function printPDF($contribIDs, &$params, $contactIds) { elseif ($contribution->_component == 'contribute') { $daoName = 'CRM_Contribute_DAO_ContributionPage'; $pageId = $contribution->contribution_page_id; - $mailElements = array( + $mailElements = [ 'title', 'receipt_from_name', 'receipt_from_email', 'cc_receipt', 'bcc_receipt', - ); + ]; CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements); $values['title'] = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails)); @@ -404,7 +381,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { // get organization address $domain = CRM_Core_BAO_Domain::getDomain(); - $locParams = array('contact_id' => $domain->contact_id); + $locParams = ['contact_id' => $domain->contact_id]; $locationDefaults = CRM_Core_BAO_Location::getValues($locParams); if (isset($locationDefaults['address'][1]['state_province_id'])) { $stateProvinceAbbreviationDomain = CRM_Core_PseudoConstant::stateProvinceAbbreviation($locationDefaults['address'][1]['state_province_id']); @@ -420,12 +397,13 @@ public static function printPDF($contribIDs, &$params, $contactIds) { } // parameters to be assign for template - $tplParams = array( + $tplParams = [ 'title' => $title, 'component' => $input['component'], 'id' => $contribution->id, 'source' => $source, - 'invoice_id' => $invoiceId, + 'invoice_number' => $contribution->invoice_number, + 'invoice_id' => $contribution->invoice_id, 'resourceBase' => $config->userFrameworkResourceURL, 'defaultCurrency' => $config->defaultCurrency, 'amount' => $contribution->total_amount, @@ -441,6 +419,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { 'pendingStatusId' => $pendingStatusId, 'cancelledStatusId' => $cancelledStatusId, 'contribution_status_id' => $contribution->contribution_status_id, + 'contributionStatusName' => CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution->contribution_status_id), 'subTotal' => $subTotal, 'street_address' => CRM_Utils_Array::value('street_address', CRM_Utils_Array::value($contribution->contact_id, $billingAddress)), 'supplemental_address_1' => CRM_Utils_Array::value('supplemental_address_1', CRM_Utils_Array::value($contribution->contact_id, $billingAddress)), @@ -462,49 +441,24 @@ public static function printPDF($contribIDs, &$params, $contactIds) { 'domain_country' => $countryDomain, 'domain_email' => CRM_Utils_Array::value('email', CRM_Utils_Array::value('1', $locationDefaults['email'])), 'domain_phone' => CRM_Utils_Array::value('phone', CRM_Utils_Array::value('1', $locationDefaults['phone'])), - ); + ]; if (isset($creditNoteId)) { $tplParams['creditnote_id'] = $creditNoteId; } - $pdfFileName = "{$invoiceId}.pdf"; - $sendTemplateParams = array( + $pdfFileName = $contribution->invoice_number . ".pdf"; + $sendTemplateParams = [ 'groupName' => 'msg_tpl_workflow_contribution', 'valueName' => 'contribution_invoice_receipt', 'contactId' => $contribution->contact_id, 'tplParams' => $tplParams, 'PDFFilename' => $pdfFileName, - ); - $session = CRM_Core_Session::singleton(); - $contactID = $session->get('userID'); - //CRM-16319 - we dont store in userID in case the user is doing multiple - //transactions etc - if (empty($contactID)) { - $contactID = $session->get('transaction.userID'); - } - // Fix Invoice email doesnot send out when completed payment using Paypal - if (empty($contactID)) { - $contactID = current($contactIds); - } - $contactEmails = CRM_Core_BAO_Email::allEmails($contactID); - $emails = array(); - $fromDisplayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', - $contactID, 'display_name' - ); - - foreach ($contactEmails as $emailId => $item) { - $email = $item['email']; - if ($email) { - $emails[$emailId] = '"' . $fromDisplayName . '" <' . $email . '> '; - } - } - $fromEmail = CRM_Utils_Array::crmArrayMerge($emails, CRM_Core_OptionGroup::values('from_email_address')); + ]; // from email address - if (isset($params['from_email_address'])) { - $fromEmailAddress = CRM_Utils_Array::value($params['from_email_address'], $fromEmail); - } + $fromEmailAddress = CRM_Utils_Array::value('from_email_address', $params); + // condition to check for download PDF Invoice or email Invoice if ($invoiceElements['createPdf']) { list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); @@ -512,11 +466,11 @@ public static function printPDF($contribIDs, &$params, $contactIds) { return $html; } else { - $mail = array( + $mail = [ 'subject' => $subject, 'body' => $message, 'html' => $html, - ); + ]; if ($mail['html']) { $messageInvoice[] = $mail['html']; } @@ -528,7 +482,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { elseif ($contribution->_component == 'contribute') { $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id); - $sendTemplateParams['tplParams'] = array_merge($tplParams, array('email_comment' => $invoiceElements['params']['email_comment'])); + $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]); $sendTemplateParams['from'] = $fromEmailAddress; $sendTemplateParams['toEmail'] = $email; $sendTemplateParams['cc'] = CRM_Utils_Array::value('cc_receipt', $values); @@ -536,14 +490,13 @@ public static function printPDF($contribIDs, &$params, $contactIds) { list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); // functions call for adding activity with attachment - $pdfFileName = "{$invoiceId}.pdf"; $fileName = self::putFile($html, $pdfFileName); self::addActivities($subject, $contribution->contact_id, $fileName, $params); } elseif ($contribution->_component == 'event') { $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id); - $sendTemplateParams['tplParams'] = array_merge($tplParams, array('email_comment' => $invoiceElements['params']['email_comment'])); + $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]); $sendTemplateParams['from'] = $fromEmailAddress; $sendTemplateParams['toEmail'] = $email; $sendTemplateParams['cc'] = CRM_Utils_Array::value('cc_confirm', $values); @@ -551,12 +504,9 @@ public static function printPDF($contribIDs, &$params, $contactIds) { list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); // functions call for adding activity with attachment - $pdfFileName = "{$invoiceId}.pdf"; $fileName = self::putFile($html, $pdfFileName); self::addActivities($subject, $contribution->contact_id, $fileName, $params); } - - CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contribution->id, 'invoice_id', $invoiceId); $invoiceTemplate->clearTemplateVars(); } @@ -565,12 +515,11 @@ public static function printPDF($contribIDs, &$params, $contactIds) { return $html; } else { - $pdfFileName = "{$invoiceId}.pdf"; - CRM_Utils_PDF_Utils::html2pdf($messageInvoice, $pdfFileName, FALSE, array( + CRM_Utils_PDF_Utils::html2pdf($messageInvoice, $pdfFileName, FALSE, [ 'margin_top' => 10, 'margin_left' => 65, 'metric' => 'px', - )); + ]); // functions call for adding activity with attachment $fileName = self::putFile($html, $pdfFileName); self::addActivities($subject, $contactIds, $fileName, $params); @@ -580,7 +529,7 @@ public static function printPDF($contribIDs, &$params, $contactIds) { } else { if ($invoiceElements['suppressedEmails']) { - $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', array(1 => $invoiceElements['suppressedEmails'])); + $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', [1 => $invoiceElements['suppressedEmails']]); $msgTitle = ts('Email Error'); $msgType = 'error'; } @@ -606,38 +555,40 @@ public static function printPDF($contribIDs, &$params, $contactIds) { * For invoices. * */ - static public function addActivities($subject, $contactIds, $fileName, $params) { + public static function addActivities($subject, $contactIds, $fileName, $params) { $session = CRM_Core_Session::singleton(); $userID = $session->get('userID'); $config = CRM_Core_Config::singleton(); $config->doNotAttachPDFReceipt = 1; if (!empty($params['output']) && $params['output'] == 'pdf_invoice') { - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', - 'Downloaded Invoice', - 'name' + $activityTypeID = CRM_Core_PseudoConstant::getKey( + 'CRM_Activity_DAO_Activity', + 'activity_type_id', + 'Downloaded Invoice' ); } else { - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', - 'Emailed Invoice', - 'name' + $activityTypeID = CRM_Core_PseudoConstant::getKey( + 'CRM_Activity_DAO_Activity', + 'activity_type_id', + 'Emailed Invoice' ); } - $activityParams = array( + $activityParams = [ 'subject' => $subject, 'source_contact_id' => $userID, 'target_contact_id' => $contactIds, 'activity_type_id' => $activityTypeID, 'activity_date_time' => date('YmdHis'), - 'attachFile_1' => array( + 'attachFile_1' => [ 'uri' => $fileName, 'type' => 'application/pdf', 'location' => $fileName, 'upload_date' => date('YmdHis'), - ), - ); + ], + ]; CRM_Activity_BAO_Activity::create($activityParams); } @@ -652,7 +603,7 @@ static public function addActivities($subject, $contactIds, $fileName, $params) * @return string * Name of file which is in pdf format */ - static public function putFile($html, $name = 'Invoice.pdf') { + public static function putFile($html, $name = 'Invoice.pdf') { $options = new Options(); $options->set('isRemoteEnabled', TRUE); @@ -671,9 +622,9 @@ static public function putFile($html, $name = 'Invoice.pdf') { */ public static function getPrintPDF() { $contributionId = CRM_Utils_Request::retrieve('id', 'Positive', CRM_Core_DAO::$_nullObject, FALSE); - $contributionIDs = array($contributionId); + $contributionIDs = [$contributionId]; $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', CRM_Core_DAO::$_nullObject, FALSE); - $params = array('output' => 'pdf_invoice'); + $params = ['output' => 'pdf_invoice']; CRM_Contribute_Form_Task_Invoice::printPDF($contributionIDs, $params, $contactId); } diff --git a/CRM/Contribute/Form/Task/PDF.php b/CRM/Contribute/Form/Task/PDF.php index 2455d38fdecd..96d9985e9ae1 100644 --- a/CRM/Contribute/Form/Task/PDF.php +++ b/CRM/Contribute/Form/Task/PDF.php @@ -1,9 +1,9 @@ _contributionIds = array($id); + $this->_contributionIds = [$id]; $this->_componentClause = " civicrm_contribution.id IN ( $id ) "; $this->_single = TRUE; $this->assign('totalSelectedContributions', 1); @@ -85,12 +85,12 @@ public function preProcess() { } $url = CRM_Utils_System::url('civicrm/contribute/search', $urlParams); - $breadCrumb = array( - array( + $breadCrumb = [ + [ 'url' => $url, 'title' => ts('Search Results'), - ), - ); + ], + ]; CRM_Contact_Form_Task_EmailCommon ::preProcessFromAddress($this, FALSE); // we have all the contribution ids, so now we get the contact ids parent::setContactIDs(); @@ -104,35 +104,38 @@ public function preProcess() { public function buildQuickForm() { $this->addElement('radio', 'output', NULL, ts('Email Receipts'), 'email_receipt', - array( + [ 'onClick' => "document.getElementById('selectPdfFormat').style.display = 'none'; - document.getElementById('selectEmailFrom').style.display = 'block';") + document.getElementById('selectEmailFrom').style.display = 'block';", + ] ); $this->addElement('radio', 'output', NULL, ts('PDF Receipts'), 'pdf_receipt', - array('onClick' => "document.getElementById('selectPdfFormat').style.display = 'block';") + [ + 'onClick' => "document.getElementById('selectPdfFormat').style.display = 'block'; + document.getElementById('selectEmailFrom').style.display = 'none';", + ] ); $this->addRule('output', ts('Selection required'), 'required'); $this->add('select', 'pdf_format_id', ts('Page Format'), - array(0 => ts('- default -')) + CRM_Core_BAO_PdfFormat::getList(TRUE) + [0 => ts('- default -')] + CRM_Core_BAO_PdfFormat::getList(TRUE) ); $this->add('checkbox', 'receipt_update', ts('Update receipt dates for these contributions'), FALSE); $this->add('checkbox', 'override_privacy', ts('Override privacy setting? (Do not email / Do not mail)'), FALSE); - $this->add('select', 'fromEmailAddress', ts('From Email'), $this->_fromEmails, FALSE, array('class' => 'crm-select2 huge')); - - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Process Receipt(s)'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Cancel'), - ), - ) - ); + $this->add('select', 'from_email_address', ts('From Email'), $this->_fromEmails, FALSE); + + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Process Receipt(s)'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -140,7 +143,7 @@ public function buildQuickForm() { */ public function setDefaultValues() { $defaultFormat = CRM_Core_BAO_PdfFormat::getDefaultValues(); - return array('pdf_format_id' => $defaultFormat['id'], 'receipt_update' => 1, 'override_privacy' => 0); + return ['pdf_format_id' => $defaultFormat['id'], 'receipt_update' => 1, 'override_privacy' => 0]; } /** @@ -148,14 +151,14 @@ public function setDefaultValues() { */ public function postProcess() { // get all the details needed to generate a receipt - $message = array(); + $message = []; $template = CRM_Core_Smarty::singleton(); $params = $this->controller->exportValues($this->_name); $elements = self::getElements($this->_contributionIds, $params, $this->_contactIds); foreach ($elements['details'] as $contribID => $detail) { - $input = $ids = $objects = array(); + $input = $ids = $objects = []; if (in_array($detail['contact'], $elements['excludeContactIds'])) { continue; @@ -190,17 +193,20 @@ public function postProcess() { CRM_Core_DAO::singleValueQuery("SELECT payment_processor_id FROM civicrm_financial_trxn WHERE trxn_id = %1 - LIMIT 1", array( - 1 => array($contribution->trxn_id, 'String'))); + LIMIT 1", [ + 1 => [$contribution->trxn_id, 'String'], + ]); // CRM_Contribute_BAO_Contribution::composeMessageArray expects mysql formatted date $objects['contribution']->receive_date = CRM_Utils_Date::isoToMysql($objects['contribution']->receive_date); - $values = array(); - if (isset($params['fromEmailAddress']) && !$elements['createPdf']) { + $values = []; + if (isset($params['from_email_address']) && !$elements['createPdf']) { + // If a logged in user from email is used rather than a domain wide from email address + // the from_email_address params key will be numerical and we need to convert it to be + // in normal from email format + $from = CRM_Utils_Mail::formatFromAddress($params['from_email_address']); // CRM-19129 Allow useres the choice of From Email to send the receipt from. - $fromEmail = $params['fromEmailAddress']; - $from = CRM_Utils_Array::value($fromEmail, $this->_emails); $fromDetails = explode(' <', $from); $input['receipt_from_email'] = substr(trim($fromDetails[1]), 0, -1); $input['receipt_from_name'] = str_replace('"', '', $fromDetails[0]); @@ -230,7 +236,7 @@ public function postProcess() { } else { if ($elements['suppressedEmails']) { - $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', array(1 => $elements['suppressedEmails'])); + $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', [1 => $elements['suppressedEmails']]); $msgTitle = ts('Email Error'); $msgType = 'error'; } @@ -258,8 +264,8 @@ public function postProcess() { * array of common elements * */ - static public function getElements($contribIds, $params, $contactIds) { - $pdfElements = array(); + public static function getElements($contribIds, $params, $contactIds) { + $pdfElements = []; $pdfElements['contribIDs'] = implode(',', $contribIds); @@ -276,14 +282,14 @@ static public function getElements($contribIds, $params, $contactIds) { $pdfElements['createPdf'] = TRUE; } - $excludeContactIds = array(); + $excludeContactIds = []; if (!$pdfElements['createPdf']) { - $returnProperties = array( + $returnProperties = [ 'email' => 1, 'do_not_email' => 1, 'is_deceased' => 1, 'on_hold' => 1, - ); + ]; list($contactDetails) = CRM_Utils_Token::getTokenDetails($contactIds, $returnProperties, FALSE, FALSE); $pdfElements['suppressedEmails'] = 0; diff --git a/CRM/Contribute/Form/Task/PDFLetter.php b/CRM/Contribute/Form/Task/PDFLetter.php index de0afcc2cbfd..03620ec4dd2c 100644 --- a/CRM/Contribute/Form/Task/PDFLetter.php +++ b/CRM/Contribute/Form/Task/PDFLetter.php @@ -1,9 +1,9 @@ skipOnHold = $this->skipDeceased = FALSE; CRM_Contact_Form_Task_PDFLetterCommon::preProcess($this); // store case id if present - $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'Positive', $this, FALSE); + $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'CommaSeparatedIntegers', $this, FALSE); + if (!empty($this->_caseId) && strpos($this->_caseId, ',')) { + $this->_caseIds = explode(',', $this->_caseId); + unset($this->_caseId); + } // retrieve contact ID if this is 'single' mode - $cid = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE); + $cid = CRM_Utils_Request::retrieve('cid', 'CommaSeparatedIntegers', $this, FALSE); $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE); if ($cid) { CRM_Contact_Form_Task_PDFLetterCommon::preProcessSingle($this, $cid); $this->_single = TRUE; - $this->_cid = $cid; } else { parent::preProcess(); @@ -81,13 +84,14 @@ public function preProcess() { * @return array * reference to the array of default values */ + /** * @return array */ public function setDefaultValues() { - $defaults = array(); + $defaults = []; if (isset($this->_activityId)) { - $params = array('id' => $this->_activityId); + $params = ['id' => $this->_activityId]; CRM_Activity_BAO_Activity::retrieve($params, $defaults); $defaults['html_message'] = CRM_Utils_Array::value('details', $defaults); } @@ -105,8 +109,8 @@ public function buildQuickForm() { //enable form element $this->assign('suppressForm', FALSE); - // use contact form as a base - CRM_Contact_Form_Task_PDFLetterCommon::buildQuickForm($this); + // Build common form elements + CRM_Contribute_Form_Task_PDFLetterCommon::buildQuickForm($this); // specific need for contributions $this->add('static', 'more_options_header', NULL, ts('Thank-you Letter Options')); @@ -114,42 +118,41 @@ public function buildQuickForm() { $this->add('checkbox', 'thankyou_update', ts('Update thank-you dates for these contributions'), FALSE); // Group options for tokens are not yet implemented. dgg - $options = array( + $options = [ '' => ts('- no grouping -'), 'contact_id' => ts('Contact'), 'contribution_recur_id' => ts('Contact and Recurring'), 'financial_type_id' => ts('Contact and Financial Type'), 'campaign_id' => ts('Contact and Campaign'), 'payment_instrument_id' => ts('Contact and Payment Method'), - ); - $this->addElement('select', 'group_by', ts('Group contributions by'), $options, array(), "
    ", FALSE); + ]; + $this->addElement('select', 'group_by', ts('Group contributions by'), $options, [], "
    ", FALSE); // this was going to be free-text but I opted for radio options in case there was a script injection risk - $separatorOptions = array('comma' => 'Comma', 'td' => 'Horizontal Table Cell', 'tr' => 'Vertical Table Cell', 'br' => 'Line Break'); + $separatorOptions = ['comma' => 'Comma', 'td' => 'Horizontal Table Cell', 'tr' => 'Vertical Table Cell', 'br' => 'Line Break']; $this->addElement('select', 'group_by_separator', ts('Separator (grouped contributions)'), $separatorOptions); - $emailOptions = array( + $emailOptions = [ '' => ts('Generate PDFs for printing (only)'), 'email' => ts('Send emails where possible. Generate printable PDFs for contacts who cannot receive email.'), 'both' => ts('Send emails where possible. Generate printable PDFs for all contacts.'), - ); + ]; if (CRM_Core_Config::singleton()->doNotAttachPDFReceipt) { $emailOptions['pdfemail'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for contacts who cannot receive email.'); $emailOptions['pdfemail_both'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for all contacts.'); } - $this->addElement('select', 'email_options', ts('Print and email options'), $emailOptions, array(), "
    ", FALSE); - - $this->addButtons(array( - array( - 'type' => 'upload', - 'name' => ts('Make Thank-you Letters'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Done'), - ), - ) - ); + $this->addElement('select', 'email_options', ts('Print and email options'), $emailOptions, [], "
    ", FALSE); + + $this->addButtons([ + [ + 'type' => 'upload', + 'name' => ts('Make Thank-you Letters'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Done'), + ], + ]); } diff --git a/CRM/Contribute/Form/Task/PDFLetterCommon.php b/CRM/Contribute/Form/Task/PDFLetterCommon.php index a010487a2a29..8650ac02c7db 100644 --- a/CRM/Contribute/Form/Task/PDFLetterCommon.php +++ b/CRM/Contribute/Form/Task/PDFLetterCommon.php @@ -6,6 +6,20 @@ */ class CRM_Contribute_Form_Task_PDFLetterCommon extends CRM_Contact_Form_Task_PDFLetterCommon { + /** + * Build the form object. + * + * @var CRM_Core_Form $form + */ + public static function buildQuickForm(&$form) { + // use contact form as a base + CRM_Contact_Form_Task_PDFLetterCommon::buildQuickForm($form); + + // Contribute PDF tasks allow you to email as well, so we need to add email address to those forms + $form->add('select', 'from_email_address', ts('From Email Address'), $form->_fromEmails, TRUE); + parent::buildQuickForm($form); + } + /** * Process the form after the input has been submitted and validated. * @@ -18,12 +32,16 @@ public static function postProcess(&$form, $formValues = NULL) { } list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($formValues); $isPDF = FALSE; - $emailParams = array(); + $emailParams = []; if (!empty($formValues['email_options'])) { $returnProperties['email'] = $returnProperties['on_hold'] = $returnProperties['is_deceased'] = $returnProperties['do_not_email'] = 1; - $emailParams = array( - 'subject' => $formValues['subject'], - ); + $emailParams = [ + 'subject' => CRM_Utils_Array::value('subject', $formValues), + 'from' => CRM_Utils_Array::value('from_email_address', $formValues), + ]; + + $emailParams['from'] = CRM_Utils_Mail::formatFromAddress($emailParams['from']); + // We need display_name for emailLetter() so add to returnProperties here $returnProperties['display_name'] = 1; if (stristr($formValues['email_options'], 'pdfemail')) { @@ -38,23 +56,22 @@ public static function postProcess(&$form, $formValues = NULL) { $updateStatus = ''; $task = 'CRM_Contribution_Form_Task_PDFLetterCommon'; $realSeparator = ', '; - $tableSeparators = array( + $tableSeparators = [ 'td' => '', 'tr' => '', - ); + ]; //the original thinking was mutliple options - but we are going with only 2 (comma & td) for now in case // there are security (& UI) issues we need to think through if (isset($formValues['group_by_separator'])) { - if (in_array($formValues['group_by_separator'], array('td', 'tr'))) { + if (in_array($formValues['group_by_separator'], ['td', 'tr'])) { $realSeparator = $tableSeparators[$formValues['group_by_separator']]; } elseif ($formValues['group_by_separator'] == 'br') { $realSeparator = "
    "; } } - $separator = '****~~~~';// a placeholder in case the separator is common in the string - e.g ', ' - $validated = FALSE; - + // a placeholder in case the separator is common in the string - e.g ', ' + $separator = '****~~~~'; $groupBy = $formValues['group_by']; // skip some contacts ? @@ -66,59 +83,53 @@ public static function postProcess(&$form, $formValues = NULL) { $contributionIDs = $form->getVar('_contributionContactIds'); } list($contributions, $contacts) = self::buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $form->_includesSoftCredits); - $html = array(); + $html = []; + $contactHtml = $emailedHtml = []; foreach ($contributions as $contributionId => $contribution) { $contact = &$contacts[$contribution['contact_id']]; - $grouped = $groupByID = 0; + $grouped = FALSE; + $groupByID = 0; if ($groupBy) { $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; $contribution = $contact['combined'][$groupBy][$groupByID]; $grouped = TRUE; } - self::assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID); - if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { - if (!$validated && in_array($realSeparator, $tableSeparators) && !self::isValidHTMLWithTableSeparator($messageToken, $html_message)) { - $realSeparator = ', '; - CRM_Core_Session::setStatus(ts('You have selected the table cell separator, but one or more token fields are not placed inside a table cell. This would result in invalid HTML, so comma separators have been used instead.')); - } - $validated = TRUE; - $html[$contributionId] = str_replace($separator, $realSeparator, self::resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator)); - $contact['is_sent'][$groupBy][$groupByID] = TRUE; + $html[$contributionId] = self::generateHtml($contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID); + $contactHtml[$contact['contact_id']][] = $html[$contributionId]; if (!empty($formValues['email_options'])) { if (self::emailLetter($contact, $html[$contributionId], $isPDF, $formValues, $emailParams)) { $emailed++; if (!stristr($formValues['email_options'], 'both')) { - unset($html[$contributionId]); + $emailedHtml[$contributionId] = TRUE; } } } + $contact['is_sent'][$groupBy][$groupByID] = TRUE; } - - // update dates (do it for each contribution including grouped recurring contribution) - //@todo - the 2 calls below bypass all hooks. Using the api would possibly be slower than one call but not than 2 + // Update receipt/thankyou dates + $contributionParams = ['id' => $contributionId]; if ($receipt_update) { - $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'receipt_date', $nowDate); - if ($result) { - $receipts++; - } + $contributionParams['receipt_date'] = $nowDate; } if ($thankyou_update) { - $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'thankyou_date', $nowDate); - if ($result) { - $thanks++; - } + $contributionParams['thankyou_date'] = $nowDate; + } + if ($receipt_update || $thankyou_update) { + civicrm_api3('Contribution', 'create', $contributionParams); + $receipts = ($receipt_update ? $receipts + 1 : $receipts); + $thanks = ($thankyou_update ? $thanks + 1 : $thanks); } } + $contactIds = array_keys($contacts); + self::createActivities($form, $html_message, $contactIds, CRM_Utils_Array::value('subject', $formValues, ts('Thank you letter')), CRM_Utils_Array::value('campaign_id', $formValues), $contactHtml); + $html = array_diff_key($html, $emailedHtml); + if (!empty($formValues['is_unit_test'])) { return $html; } - //createActivities requires both $form->_contactIds and $contacts - - //@todo - figure out why - $form->_contactIds = array_keys($contacts); - self::createActivities($form, $html_message, $form->_contactIds); //CRM-19761 if (!empty($html)) { @@ -135,13 +146,13 @@ public static function postProcess(&$form, $formValues = NULL) { $form->postProcessHook(); if ($emailed) { - $updateStatus = ts('Receipts have been emailed to %1 contributions.', array(1 => $emailed)); + $updateStatus = ts('Receipts have been emailed to %1 contributions.', [1 => $emailed]); } if ($receipts) { - $updateStatus = ts('Receipt date has been updated for %1 contributions.', array(1 => $receipts)); + $updateStatus = ts('Receipt date has been updated for %1 contributions.', [1 => $receipts]); } if ($thanks) { - $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', array(1 => $thanks)); + $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', [1 => $thanks]); } if ($updateStatus) { @@ -149,7 +160,7 @@ public static function postProcess(&$form, $formValues = NULL) { } if (!empty($html)) { // ie. we have only sent emails - lets no show a white screen - CRM_Utils_System::civiExit(1); + CRM_Utils_System::civiExit(); } } @@ -164,7 +175,7 @@ public static function postProcess(&$form, $formValues = NULL) { * @return bool */ public static function isValidHTMLWithTableSeparator($tokens, $html) { - $relevantEntities = array('contribution'); + $relevantEntities = ['contribution']; foreach ($relevantEntities as $entity) { if (isset($tokens[$entity]) && is_array($tokens[$entity])) { foreach ($tokens[$entity] as $token) { @@ -181,17 +192,17 @@ public static function isValidHTMLWithTableSeparator($tokens, $html) { * Check that the token only appears in a table cell. The '' separator cannot otherwise work * Calculate the number of times it appears IN the cell & the number of times it appears - should be the same! * - * @param $token - * @param $entity - * @param $textToSearch + * @param string $token + * @param string $entity + * @param string $textToSearch * * @return bool */ public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) { - $tokenToMatch = $entity . '.' . $token; - $dontCare = array(); - $within = preg_match_all("||si'; + $within = preg_match_all($pattern, $textToSearch); + $total = preg_match_all("|{" . $tokenToMatch . "}|", $textToSearch); return ($within == $total); } @@ -201,14 +212,14 @@ public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) { * @param array $contact * @param array $contribution * @param array $messageToken - * @param array $categories * @param bool $grouped * Does this letter represent more than one contribution. * @param string $separator * What is the preferred letter separator. * @return string */ - private static function resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator) { + private static function resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator) { + $categories = self::getTokenCategories(); $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact, TRUE, $messageToken); if ($grouped) { $tokenHtml = CRM_Utils_Token::replaceMultipleContributionTokens($separator, $tokenHtml, $contribution, TRUE, $messageToken); @@ -244,20 +255,10 @@ private static function resolveTokens($html_message, $contact, $contribution, $m * @return array */ public static function buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $isIncludeSoftCredits) { - $contributions = $contacts = $notSent = array(); + $contributions = $contacts = []; foreach ($contributionIDs as $item => $contributionId) { - // get contribution information - - // basic return attributes needed, see below for there usage - $returnValues = array('contact_id', 'total_amount'); - if (!empty($messageToken['contribution'])) { - $returnValues = array_merge($messageToken['contribution'], $returnValues); - } - // retrieve contribution tokens listed in $returnProperties using Contribution.Get API - $contribution = civicrm_api3('Contribution', 'getsingle', array( - 'id' => $contributionId, - 'return' => $returnValues, - )); + $contribution = CRM_Contribute_BAO_Contribution::getContributionTokenValues($contributionId, $messageToken)['values'][$contributionId]; + $contribution['campaign'] = CRM_Utils_Array::value('contribution_campaign_title', $contribution); $contributions[$contributionId] = $contribution; if ($isIncludeSoftCredits) { @@ -269,9 +270,9 @@ public static function buildContributionArray($groupBy, $contributionIDs, $retur $contactID = $contribution['contact_id']; } if (!isset($contacts[$contactID])) { - $contacts[$contactID] = array(); + $contacts[$contactID] = []; $contacts[$contactID]['contact_aggregate'] = 0; - $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = array(); + $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = []; } $contacts[$contactID]['contact_aggregate'] += $contribution['total_amount']; @@ -293,7 +294,7 @@ public static function buildContributionArray($groupBy, $contributionIDs, $retur // Hooks allow more nuanced smarty usage here. CRM_Core_Smarty::singleton()->assign('contributions', $contributions); foreach ($contacts as $contactID => $contact) { - $tokenResolvedContacts = CRM_Utils_Token::getTokenDetails(array('contact_id' => $contactID), + $tokenResolvedContacts = CRM_Utils_Token::getTokenDetails(['contact_id' => $contactID], $returnProperties, $skipOnHold, $skipDeceased, @@ -303,7 +304,7 @@ public static function buildContributionArray($groupBy, $contributionIDs, $retur ); $contacts[$contactID] = array_merge($tokenResolvedContacts[0][$contactID], $contact); } - return array($contributions, $contacts); + return [$contributions, $contacts]; } /** @@ -335,9 +336,6 @@ public static function combineContributions($existing, $contribution, $separator * @param int $groupByID */ public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) { - if (!defined('CIVICRM_MAIL_SMARTY') || !CIVICRM_MAIL_SMARTY) { - return; - } CRM_Core_Smarty::singleton()->assign('contact_aggregate', $contact['contact_aggregate']); CRM_Core_Smarty::singleton() ->assign('contributions', array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID])); @@ -357,29 +355,32 @@ public static function assignCombinedContributionValues($contact, $contributions * * @return bool */ - public static function emailLetter($contact, $html, $is_pdf, $format = array(), $params = array()) { + public static function emailLetter($contact, $html, $is_pdf, $format = [], $params = []) { try { if (empty($contact['email'])) { return FALSE; } - $mustBeEmpty = array('do_not_email', 'is_deceased', 'on_hold'); + $mustBeEmpty = ['do_not_email', 'is_deceased', 'on_hold']; foreach ($mustBeEmpty as $emptyField) { if (!empty($contact[$emptyField])) { return FALSE; } } - $defaults = array( + $defaults = [ 'toName' => $contact['display_name'], 'toEmail' => $contact['email'], 'text' => '', 'html' => $html, - ); + ]; if (empty($params['from'])) { $emails = CRM_Core_BAO_Email::getFromEmail(); $emails = array_keys($emails); $defaults['from'] = array_pop($emails); } + else { + $defaults['from'] = $params['from']; + } if (!empty($params['subject'])) { $defaults['subject'] = $params['subject']; } @@ -388,7 +389,7 @@ public static function emailLetter($contact, $html, $is_pdf, $format = array(), } if ($is_pdf) { $defaults['html'] = ts('Please see attached'); - $defaults['attachments'] = array(CRM_Utils_Mail::appendPDF('ThankYou.pdf', $html, $format)); + $defaults['attachments'] = [CRM_Utils_Mail::appendPDF('ThankYou.pdf', $html, $format)]; } $params = array_merge($defaults); return CRM_Utils_Mail::send($params); @@ -398,4 +399,39 @@ public static function emailLetter($contact, $html, $is_pdf, $format = array(), } } + /** + * @param $contact + * @param $formValues + * @param $contribution + * @param $groupBy + * @param $contributions + * @param $realSeparator + * @param $tableSeparators + * @param $messageToken + * @param $html_message + * @param $separator + * @param $categories + * @param bool $grouped + * @param int $groupByID + * + * @return string + */ + protected static function generateHtml(&$contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID) { + static $validated = FALSE; + $html = NULL; + + self::assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID); + + if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { + if (!$validated && in_array($realSeparator, $tableSeparators) && !self::isValidHTMLWithTableSeparator($messageToken, $html_message)) { + $realSeparator = ', '; + CRM_Core_Session::setStatus(ts('You have selected the table cell separator, but one or more token fields are not placed inside a table cell. This would result in invalid HTML, so comma separators have been used instead.')); + } + $validated = TRUE; + $html = str_replace($separator, $realSeparator, self::resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator)); + } + + return $html; + } + } diff --git a/CRM/Contribute/Form/Task/PickProfile.php b/CRM/Contribute/Form/Task/PickProfile.php index 3d445df4fc9c..9d45ddafcd21 100644 --- a/CRM/Contribute/Form/Task/PickProfile.php +++ b/CRM/Contribute/Form/Task/PickProfile.php @@ -1,9 +1,9 @@ _contributionIds) > $this->_maxContributions) { - CRM_Core_Session::setStatus(ts("The maximum number of contributions you can select for Update multiple contributions is %1. You have selected %2. Please select fewer contributions from your search results and try again.", array( - 1 => $this->_maxContributions, - 2 => count($this->_contributionIds), - )), ts('Update multiple records error'), 'error'); + CRM_Core_Session::setStatus(ts("The maximum number of contributions you can select for Update multiple contributions is %1. You have selected %2. Please select fewer contributions from your search results and try again.", [ + 1 => $this->_maxContributions, + 2 => count($this->_contributionIds), + ]), ts('Update multiple records error'), 'error'); $validate = TRUE; } @@ -85,18 +87,18 @@ public function preProcess() { */ public function buildQuickForm() { - $types = array('Contribution'); + $types = ['Contribution']; $profiles = CRM_Core_BAO_UFGroup::getProfiles($types, TRUE); if (empty($profiles)) { - CRM_Core_Session::setStatus(ts("You will need to create a Profile containing the %1 fields you want to edit before you can use Update multiple contributions. Navigate to Administer CiviCRM > Customize Data and Screens > CiviCRM Profile to configure a Profile. Consult the online Administrator documentation for more information.", array(1 => $types[0])), ts('Profile Required'), 'error'); + CRM_Core_Session::setStatus(ts("You will need to create a Profile containing the %1 fields you want to edit before you can use Update multiple contributions. Navigate to Administer CiviCRM > Customize Data and Screens > CiviCRM Profile to configure a Profile. Consult the online Administrator documentation for more information.", [1 => $types[0]]), ts('Profile Required'), 'error'); CRM_Utils_System::redirect($this->_userContext); } $ufGroupElement = $this->add('select', 'uf_group_id', ts('Select Profile'), - array( + [ '' => ts('- select profile -'), - ) + $profiles, TRUE + ] + $profiles, TRUE ); $this->addDefaultButtons(ts('Continue')); } @@ -105,7 +107,7 @@ public function buildQuickForm() { * Add local and global form rules. */ public function addRules() { - $this->addFormRule(array('CRM_Contribute_Form_Task_PickProfile', 'formRule')); + $this->addFormRule(['CRM_Contribute_Form_Task_PickProfile', 'formRule']); } /** diff --git a/CRM/Contribute/Form/Task/Print.php b/CRM/Contribute/Form/Task/Print.php index dfb89f892559..58055ce580e5 100644 --- a/CRM/Contribute/Form/Task/Print.php +++ b/CRM/Contribute/Form/Task/Print.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Print Contributions'), - 'js' => array('onclick' => 'window.print()'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Done'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Print Contributions'), + 'js' => ['onclick' => 'window.print()'], + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Done'), + ], + ]); } /** diff --git a/CRM/Contribute/Form/Task/Result.php b/CRM/Contribute/Form/Task/Result.php index 62259940b8cc..11ff679294d5 100644 --- a/CRM/Contribute/Form/Task/Result.php +++ b/CRM/Contribute/Form/Task/Result.php @@ -1,9 +1,9 @@ addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Contribute/Form/Task/SearchTaskHookSample.php b/CRM/Contribute/Form/Task/SearchTaskHookSample.php index 9e01ea3d4832..81a3f827df1f 100644 --- a/CRM/Contribute/Form/Task/SearchTaskHookSample.php +++ b/CRM/Contribute/Form/Task/SearchTaskHookSample.php @@ -1,9 +1,9 @@ _contributionIds); @@ -55,17 +55,15 @@ public function preProcess() { INNER JOIN civicrm_contact ct ON ( co.contact_id = ct.id ) WHERE co.id IN ( $contribIDs )"; - $dao = CRM_Core_DAO::executeQuery($query, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $rows[] = array( + $rows[] = [ 'display_name' => $dao->display_name, 'amount' => $dao->amount, 'source' => $dao->source, 'receive_date' => $dao->receive_date, - ); + ]; } $this->assign('rows', $rows); } @@ -74,14 +72,13 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $this->addButtons(array( - array( - 'type' => 'done', - 'name' => ts('Done'), - 'isDefault' => TRUE, - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'done', + 'name' => ts('Done'), + 'isDefault' => TRUE, + ], + ]); } } diff --git a/CRM/Contribute/Form/Task/Status.php b/CRM/Contribute/Form/Task/Status.php index a3535e5cbefe..7642a9172bb0 100644 --- a/CRM/Contribute/Form/Task/Status.php +++ b/CRM/Contribute/Form/Task/Status.php @@ -1,9 +1,9 @@ _contributionIds = array($id); + $this->_contributionIds = [$id]; $this->_componentClause = " civicrm_contribution.id IN ( $id ) "; $this->_single = TRUE; $this->assign('totalSelectedContributions', 1); @@ -70,9 +70,7 @@ public function preProcess() { FROM civicrm_contribution WHERE contribution_status_id != 2 AND {$this->_componentClause}"; - $count = CRM_Core_DAO::singleValueQuery($query, - CRM_Core_DAO::$_nullArray - ); + $count = CRM_Core_DAO::singleValueQuery($query); if ($count != 0) { CRM_Core_Error::statusBounce(ts('Please select only online contributions with Pending status.')); } @@ -86,10 +84,16 @@ public function preProcess() { * Build the form object. */ public function buildQuickForm() { - $status = CRM_Contribute_PseudoConstant::contributionStatus(); - unset($status[2]); - unset($status[5]); - unset($status[6]); + $status = CRM_Contribute_BAO_Contribution_Utils::getContributionStatuses( + 'contribution', $this->_contributionIds[0] + ); + $byName = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); + // FIXME: if it's invalid to transition from Pending to + // In Progress or Overdue, we should move that logic to + // CRM_Contribute_BAO_Contribution_Utils::getContributionStatuses. + foreach (['Pending', 'In Progress', 'Overdue'] as $suppress) { + unset($status[CRM_Utils_Array::key($suppress, $byName)]); + } $this->add('select', 'contribution_status_id', ts('Contribution Status'), $status, @@ -110,16 +114,14 @@ public function buildQuickForm() { civicrm_contribution co WHERE co.contact_id = c.id AND co.id IN ( $contribIDs )"; - $dao = CRM_Core_DAO::executeQuery($query, - CRM_Core_DAO::$_nullArray - ); + $dao = CRM_Core_DAO::executeQuery($query); // build a row for each contribution id - $this->_rows = array(); + $this->_rows = []; $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Contribution'); - $defaults = array(); - $now = date("m/d/Y"); - $paidByOptions = array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::paymentInstrument(); + $defaults = []; + $now = date("Y-m-d"); + $paidByOptions = ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::paymentInstrument(); while ($dao->fetch()) { $row['contact_id'] = $dao->contact_id; @@ -131,7 +133,7 @@ public function buildQuickForm() { $this->addRule("trxn_id_{$row['contribution_id']}", ts('This Transaction ID already exists in the database. Include the account number for checks.'), 'objectExists', - array('CRM_Contribute_DAO_Contribution', $dao->contribution_id, 'trxn_id') + ['CRM_Contribute_DAO_Contribution', $dao->contribution_id, 'trxn_id'] ); $row['fee_amount'] = &$this->add('text', "fee_amount_{$row['contribution_id']}", ts('Fee Amount'), @@ -140,9 +142,7 @@ public function buildQuickForm() { $this->addRule("fee_amount_{$row['contribution_id']}", ts('Please enter a valid amount.'), 'money'); $defaults["fee_amount_{$row['contribution_id']}"] = 0.0; - $row['trxn_date'] = $this->addDate("trxn_date_{$row['contribution_id']}", FALSE, - ts('Receipt Date'), array('formatType' => 'activityDate') - ); + $row['trxn_date'] = $this->add('datepicker', "trxn_date_{$row['contribution_id']}", ts('Transaction Date'), [], FALSE, ['time' => FALSE]); $defaults["trxn_date_{$row['contribution_id']}"] = $now; $this->add("text", "check_number_{$row['contribution_id']}", ts('Check Number')); @@ -156,20 +156,19 @@ public function buildQuickForm() { $this->assign_by_ref('rows', $this->_rows); $this->setDefaults($defaults); - $this->addButtons(array( - array( - 'type' => 'next', - 'name' => ts('Update Pending Status'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'back', - 'name' => ts('Cancel'), - ), - ) - ); - - $this->addFormRule(array('CRM_Contribute_Form_Task_Status', 'formRule')); + $this->addButtons([ + [ + 'type' => 'next', + 'name' => ts('Update Pending Status'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'back', + 'name' => ts('Cancel'), + ], + ]); + + $this->addFormRule(['CRM_Contribute_Form_Task_Status', 'formRule']); } /** @@ -182,7 +181,7 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields) { - $seen = $errors = array(); + $seen = $errors = []; foreach ($fields as $name => $value) { if (strpos($name, 'trxn_id_') !== FALSE) { if ($fields[$name]) { @@ -196,7 +195,7 @@ public static function formRule($fields) { if ((strpos($name, 'check_number_') !== FALSE) && $value) { $contribID = substr($name, 13); - if ($fields["payment_instrument_id_{$contribID}"] != CRM_Core_OptionGroup::getValue('payment_instrument', 'Check', 'name')) { + if ($fields["payment_instrument_id_{$contribID}"] != CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', 'Check')) { $errors["payment_instrument_id_{$contribID}"] = ts("Payment Method should be Check when a check number is entered for a contribution."); } } @@ -239,7 +238,7 @@ public static function processForm($form, $params) { // for each contribution id, we just call the baseIPN stuff foreach ($form->_rows as $row) { - $input = $ids = $objects = array(); + $input = $ids = $objects = []; $input['component'] = $details[$row['contribution_id']]['component']; $ids['contact'] = $row['contact_id']; @@ -294,7 +293,7 @@ public static function processForm($form, $params) { else { $input['trxn_id'] = $contribution->invoice_id; } - $input['trxn_date'] = CRM_Utils_Date::processDate($params["trxn_date_{$row['contribution_id']}"], date('H:i:s')); + $input['trxn_date'] = $params["trxn_date_{$row['contribution_id']}"] . ' ' . date('H:i:s'); // @todo calling baseIPN like this is a pattern in it's last gasps. Call contribute.completetransaction api. $baseIPN->completeTransaction($input, $ids, $objects, $transaction, FALSE); @@ -305,11 +304,14 @@ public static function processForm($form, $params) { } /** - * @param $contributionIDs + * @param string $contributionIDs * * @return array */ public static function &getDetails($contributionIDs) { + if (empty($contributionIDs)) { + return []; + } $query = " SELECT c.id as contribution_id, c.contact_id as contact_id , @@ -322,18 +324,15 @@ public static function &getDetails($contributionIDs) { LEFT JOIN civicrm_participant p ON pp.participant_id = p.id WHERE c.id IN ( $contributionIDs )"; - $rows = array(); - $dao = CRM_Core_DAO::executeQuery($query, - CRM_Core_DAO::$_nullArray - ); - $rows = array(); + $rows = []; + $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { $rows[$dao->contribution_id]['component'] = $dao->participant_id ? 'event' : 'contribute'; $rows[$dao->contribution_id]['contact'] = $dao->contact_id; if ($dao->membership_id) { if (!array_key_exists('membership', $rows[$dao->contribution_id])) { - $rows[$dao->contribution_id]['membership'] = array(); + $rows[$dao->contribution_id]['membership'] = []; } $rows[$dao->contribution_id]['membership'][] = $dao->membership_id; } diff --git a/CRM/Contribute/Form/UpdateBilling.php b/CRM/Contribute/Form/UpdateBilling.php index 97d358859b4d..9ab033a5bdd8 100644 --- a/CRM/Contribute/Form/UpdateBilling.php +++ b/CRM/Contribute/Form/UpdateBilling.php @@ -1,9 +1,9 @@ _mid = CRM_Utils_Request::retrieve('mid', 'Integer', $this, FALSE); - $this->_crid = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); + parent::preProcess(); if ($this->_crid) { - $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_crid, 'recur', 'info'); - $this->_paymentProcessor['object'] = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_crid, 'recur', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_crid); - // Are we cancelling a recurring contribution that is linked to an auto-renew membership? if ($this->_subscriptionDetails->membership_id) { $this->_mid = $this->_subscriptionDetails->membership_id; } } - $this->_coid = CRM_Utils_Request::retrieve('coid', 'Integer', $this, FALSE); if ($this->_coid) { $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'info'); $this->_paymentProcessor['object'] = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_coid, 'contribution'); } if ($this->_mid) { @@ -87,15 +73,8 @@ public function preProcess() { if ((!$this->_crid && !$this->_coid && !$this->_mid) || (!$this->_subscriptionDetails)) { CRM_Core_Error::fatal('Required information missing.'); } - if (!CRM_Core_Permission::check('edit contributions')) { - $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE); - if (!CRM_Contact_BAO_Contact_Utils::validChecksum($this->_subscriptionDetails->contact_id, $userChecksum)) { - CRM_Core_Error::fatal(ts('You do not have permission to cancel subscription.')); - } - $this->_selfService = TRUE; - } - if (!$this->_paymentProcessor['object']->isSupported('updateSubscriptionBillingInfo')) { + if (!$this->_paymentProcessor['object']->supports('updateSubscriptionBillingInfo')) { CRM_Core_Error::fatal(ts("%1 processor doesn't support updating subscription billing details.", array(1 => $this->_paymentProcessor['object']->_processorName) )); @@ -173,22 +152,21 @@ public function setDefaultValues() { */ public function buildQuickForm() { $type = 'next'; - if ($this->_selfService) { + if ($this->isSelfService()) { $type = 'submit'; } $this->addButtons(array( - array( - 'type' => $type, - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + array( + 'type' => $type, + 'name' => ts('Save'), + 'isDefault' => TRUE, + ), + array( + 'type' => 'cancel', + 'name' => ts('Cancel'), + ), + )); CRM_Core_Payment_Form::buildPaymentForm($this, $this->_paymentProcessor, TRUE, TRUE); $this->addFormRule(array('CRM_Contribute_Form_UpdateBilling', 'formRule'), $this); @@ -335,17 +313,15 @@ public function postProcess() { $activityParams = array( 'source_contact_id' => $this->_subscriptionDetails->contact_id, - 'activity_type_id' => CRM_Core_OptionGroup::getValue('activity_type', - 'Update Recurring Contribution Billing Details', - 'name' + 'activity_type_id' => CRM_Core_PseudoConstant::getKey( + 'CRM_Activity_BAO_Activity', + 'activity_type_id', + 'Update Recurring Contribution Billing Details' ), 'subject' => ts('Recurring Contribution Billing Details Updated'), 'details' => $message, 'activity_date_time' => date('YmdHis'), - 'status_id' => CRM_Core_OptionGroup::getValue('activity_status', - 'Completed', - 'name' - ), + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'), ); $session = CRM_Core_Session::singleton(); $cid = $session->get('userID'); diff --git a/CRM/Contribute/Form/UpdateSubscription.php b/CRM/Contribute/Form/UpdateSubscription.php index 1baf49654bd5..43c94c4caed0 100644 --- a/CRM/Contribute/Form/UpdateSubscription.php +++ b/CRM/Contribute/Form/UpdateSubscription.php @@ -1,9 +1,9 @@ setAction(CRM_Core_Action::UPDATE); - $this->contributionRecurID = CRM_Utils_Request::retrieve('crid', 'Integer', $this, FALSE); - if ($this->contributionRecurID) { - $this->_paymentProcessor = CRM_Contribute_BAO_ContributionRecur::getPaymentProcessor($this->contributionRecurID); - if (!$this->_paymentProcessor) { - CRM_Core_Error::statusBounce(ts('There is no valid processor for this subscription so it cannot be edited.')); - } - $this->_paymentProcessorObj = $this->_paymentProcessor['object']; - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->contributionRecurID); - } - - $this->_coid = CRM_Utils_Request::retrieve('coid', 'Integer', $this, FALSE); if ($this->_coid) { $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'info'); + // @todo test & replace with $this->_paymentProcessorObj = Civi\Payment\System::singleton()->getById($this->_paymentProcessor['id']); $this->_paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($this->_coid, 'contribute', 'obj'); - $this->_subscriptionDetails = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($this->_coid, 'contribution'); $this->contributionRecurID = $this->_subscriptionDetails->recur_id; } elseif ($this->contributionRecurID) { @@ -105,17 +84,20 @@ public function preProcess() { } if ($this->_subscriptionDetails->membership_id && $this->_subscriptionDetails->auto_renew) { - CRM_Core_Error::statusBounce(ts('You cannot update the subscription.')); - } - - if (!CRM_Core_Permission::check('edit contributions')) { - $userChecksum = CRM_Utils_Request::retrieve('cs', 'String', $this, FALSE); - if (!CRM_Contact_BAO_Contact_Utils::validChecksum($this->_subscriptionDetails->contact_id, $userChecksum)) { - CRM_Core_Error::statusBounce(ts('You do not have permission to update subscription.')); + // Add Membership details to form + $membership = civicrm_api3('Membership', 'get', [ + 'contribution_recur_id' => $this->contributionRecurID, + ]); + if (!empty($membership['count'])) { + $membershipDetails = reset($membership['values']); + $values['membership_id'] = $membershipDetails['id']; + $values['membership_name'] = $membershipDetails['membership_name']; } - $this->_selfService = TRUE; + $this->assign('recurMembership', $values); + $this->assign('contactId', $this->_subscriptionDetails->contact_id); } - $this->assign('self_service', $this->_selfService); + + $this->assign('self_service', $this->isSelfService()); $this->editableScheduleFields = $this->_paymentProcessorObj->getEditableRecurringScheduleFields(); @@ -127,17 +109,21 @@ public function preProcess() { else { $this->assign('changeHelpText', $changeHelpText); } - $alreadyHardCodedFields = array('amount', 'installments'); + $alreadyHardCodedFields = ['amount', 'installments']; foreach ($this->editableScheduleFields as $editableScheduleField) { if (!in_array($editableScheduleField, $alreadyHardCodedFields)) { - $this->addField($editableScheduleField, array('entity' => 'ContributionRecur')); + $this->addField($editableScheduleField, ['entity' => 'ContributionRecur'], FALSE, FALSE); } } + // when custom data is included in this page + if (!empty($_POST['hidden_custom']) && !$this->isSelfService()) { + CRM_Custom_Form_CustomData::preProcess($this, NULL, NULL, 1, 'ContributionRecur', $this->contributionRecurID); + CRM_Custom_Form_CustomData::buildQuickForm($this); + CRM_Custom_Form_CustomData::setDefaultValues($this); + } + $this->assign('editableScheduleFields', array_diff($this->editableScheduleFields, $alreadyHardCodedFields)); - $this->assign('paymentProcessor', $this->_paymentProcessor); - $this->assign('frequency_unit', $this->_subscriptionDetails->frequency_unit); - $this->assign('frequency_interval', $this->_subscriptionDetails->frequency_interval); if ($this->_subscriptionDetails->contact_id) { list($this->_donorDisplayName, $this->_donorEmail) = CRM_Contact_BAO_Contact::getContactDetails($this->_subscriptionDetails->contact_id); @@ -155,14 +141,14 @@ public function preProcess() { * Note that in edit/view mode the default values are retrieved from the database. */ public function setDefaultValues() { - $this->_defaults = array(); + $this->_defaults = []; $this->_defaults['amount'] = $this->_subscriptionDetails->amount; $this->_defaults['installments'] = $this->_subscriptionDetails->installments; $this->_defaults['campaign_id'] = $this->_subscriptionDetails->campaign_id; $this->_defaults['financial_type_id'] = $this->_subscriptionDetails->financial_type_id; $this->_defaults['is_notify'] = 1; foreach ($this->editableScheduleFields as $field) { - $this->_defaults[$field] = $this->_subscriptionDetails->$field; + $this->_defaults[$field] = isset($this->_subscriptionDetails->$field) ? $this->_subscriptionDetails->$field : NULL; } return $this->_defaults; @@ -173,16 +159,16 @@ public function setDefaultValues() { */ public function buildQuickForm() { // CRM-16398: If current recurring contribution got > 1 lineitems then make amount field readonly - $amtAttr = array('size' => 20); + $amtAttr = ['size' => 20]; $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_coid); if (count($lineItems) > 1) { - $amtAttr += array('readonly' => TRUE); + $amtAttr += ['readonly' => TRUE]; } $this->addMoney('amount', ts('Recurring Contribution Amount'), TRUE, $amtAttr, TRUE, 'currency', $this->_subscriptionDetails->currency, TRUE ); - $this->add('text', 'installments', ts('Number of Installments'), array('size' => 20), FALSE); + $this->add('text', 'installments', ts('Number of Installments'), ['size' => 20], FALSE); if ($this->_donorEmail) { $this->add('checkbox', 'is_notify', ts('Notify Contributor?')); @@ -193,27 +179,30 @@ public function buildQuickForm() { } if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($this->contributionRecurID)) { - $this->addEntityRef('financial_type_id', ts('Financial Type'), array('entity' => 'FinancialType'), !$this->_selfService); + $this->addEntityRef('financial_type_id', ts('Financial Type'), ['entity' => 'FinancialType'], !$this->isSelfService()); } + // Add custom data + $this->assign('customDataType', 'ContributionRecur'); + $this->assign('entityID', $this->contributionRecurID); + $type = 'next'; - if ($this->_selfService) { + if ($this->isSelfService()) { $type = 'submit'; } // define the buttons - $this->addButtons(array( - array( - 'type' => $type, - 'name' => ts('Save'), - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => $type, + 'name' => ts('Save'), + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -223,7 +212,7 @@ public function postProcess() { // store the submitted values in an array $params = $this->exportValues(); - if ($this->_selfService && $this->_donorEmail) { + if ($this->isSelfService() && $this->_donorEmail) { // for self service force notify $params['is_notify'] = 1; } @@ -234,7 +223,7 @@ public function postProcess() { $params['subscriptionId'] = $this->_subscriptionDetails->subscription_id; $updateSubscription = TRUE; - if ($this->_paymentProcessorObj->isSupported('changeSubscriptionAmount')) { + if ($this->_paymentProcessorObj->supports('changeSubscriptionAmount')) { $updateSubscription = $this->_paymentProcessorObj->changeSubscriptionAmount($message, $params); } if (is_a($updateSubscription, 'CRM_Core_Error')) { @@ -244,15 +233,17 @@ public function postProcess() { $msgType = 'error'; } elseif ($updateSubscription) { + // Handle custom data + $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params, $this->contributionRecurID, 'ContributionRecur'); // save the changes - $result = CRM_Contribute_BAO_ContributionRecur::add($params); + CRM_Contribute_BAO_ContributionRecur::add($params); $status = ts('Recurring contribution has been updated to: %1, every %2 %3(s) for %4 installments.', - array( + [ 1 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency), 2 => $this->_subscriptionDetails->frequency_interval, 3 => $this->_subscriptionDetails->frequency_unit, 4 => $params['installments'], - ) + ] ); $msgTitle = ts('Update Success'); @@ -262,10 +253,10 @@ public function postProcess() { if ($this->_subscriptionDetails->amount != $params['amount']) { $message .= "
    " . ts("Recurring contribution amount has been updated from %1 to %2 for this subscription.", - array( + [ 1 => CRM_Utils_Money::format($this->_subscriptionDetails->amount, $this->_subscriptionDetails->currency), 2 => CRM_Utils_Money::format($params['amount'], $this->_subscriptionDetails->currency), - )) . ' '; + ]) . ' '; if ($this->_subscriptionDetails->amount < $params['amount']) { $msg = ts('Recurring Contribution Updated - increased installment amount'); } @@ -275,26 +266,21 @@ public function postProcess() { } if ($this->_subscriptionDetails->installments != $params['installments']) { - $message .= "
    " . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", array( - 1 => $this->_subscriptionDetails->installments, - 2 => $params['installments'], - )) . ' '; + $message .= "
    " . ts("Recurring contribution installments have been updated from %1 to %2 for this subscription.", [ + 1 => $this->_subscriptionDetails->installments, + 2 => $params['installments'], + ]) . ' '; } - $activityParams = array( + $activityParams = [ 'source_contact_id' => $contactID, - 'activity_type_id' => CRM_Core_OptionGroup::getValue('activity_type', - 'Update Recurring Contribution', - 'name' - ), + 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Update Recurring Contribution'), 'subject' => $msg, 'details' => $message, 'activity_date_time' => date('YmdHis'), - 'status_id' => CRM_Core_OptionGroup::getValue('activity_status', - 'Completed', - 'name' - ), - ); + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed'), + ]; + $session = CRM_Core_Session::singleton(); $cid = $session->get('userID'); @@ -308,11 +294,11 @@ public function postProcess() { // send notification if ($this->_subscriptionDetails->contribution_page_id) { CRM_Core_DAO::commonRetrieveAll('CRM_Contribute_DAO_ContributionPage', 'id', - $this->_subscriptionDetails->contribution_page_id, $value, array( + $this->_subscriptionDetails->contribution_page_id, $value, [ 'title', 'receipt_from_name', 'receipt_from_email', - ) + ] ); $receiptFrom = '"' . CRM_Utils_Array::value('receipt_from_name', $value[$this->_subscriptionDetails->contribution_page_id]) . '" <' . $value[$this->_subscriptionDetails->contribution_page_id]['receipt_from_email'] . '>'; } @@ -323,17 +309,17 @@ public function postProcess() { list($donorDisplayName, $donorEmail) = CRM_Contact_BAO_Contact::getContactDetails($contactID); - $tplParams = array( + $tplParams = [ 'recur_frequency_interval' => $this->_subscriptionDetails->frequency_interval, 'recur_frequency_unit' => $this->_subscriptionDetails->frequency_unit, 'amount' => CRM_Utils_Money::format($params['amount']), 'installments' => $params['installments'], - ); + ]; - $tplParams['contact'] = array('display_name' => $donorDisplayName); + $tplParams['contact'] = ['display_name' => $donorDisplayName]; $tplParams['receipt_from_email'] = $receiptFrom; - $sendTemplateParams = array( + $sendTemplateParams = [ 'groupName' => 'msg_tpl_workflow_contribution', 'valueName' => 'contribution_recurring_edit', 'contactId' => $contactID, @@ -343,7 +329,7 @@ public function postProcess() { 'from' => $receiptFrom, 'toName' => $donorDisplayName, 'toEmail' => $donorEmail, - ); + ]; list($sent) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams); } } @@ -363,11 +349,4 @@ public function postProcess() { } } - /** - * Explicitly declare the form context. - */ - public function getDefaultContext() { - return 'create'; - } - } diff --git a/CRM/Contribute/Import/Controller.php b/CRM/Contribute/Import/Controller.php index 32eef8a8b3be..7860644926cb 100644 --- a/CRM/Contribute/Import/Controller.php +++ b/CRM/Contribute/Import/Controller.php @@ -1,9 +1,9 @@ addActions($config->uploadDir, array('uploadFile')); + $this->addActions($config->uploadDir, ['uploadFile']); } } diff --git a/CRM/Contribute/Import/Field.php b/CRM/Contribute/Import/Field.php index 686440fc1538..83943e405a39 100644 --- a/CRM/Contribute/Import/Field.php +++ b/CRM/Contribute/Import/Field.php @@ -1,9 +1,9 @@ _value); case 'trxn_id': - static $seenTrxnIds = array(); + static $seenTrxnIds = []; if (in_array($this->_value, $seenTrxnIds)) { return FALSE; } diff --git a/CRM/Contribute/Import/Form/DataSource.php b/CRM/Contribute/Import/Form/DataSource.php index 6338597b5c1a..ff0ee51e5479 100644 --- a/CRM/Contribute/Import/Form/DataSource.php +++ b/CRM/Contribute/Import/Form/DataSource.php @@ -1,9 +1,9 @@ createElement('radio', NULL, NULL, ts('Insert new contributions'), CRM_Import_Parser::DUPLICATE_SKIP ); @@ -57,9 +57,9 @@ public function buildQuickForm() { ts('Import mode') ); - $this->setDefaults(array('onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP)); + $this->setDefaults(['onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP]); - $this->addElement('submit', 'loadMapping', ts('Load Mapping'), NULL, array('onclick' => 'checkSelect()')); + $this->addElement('submit', 'loadMapping', ts('Load Mapping'), NULL, ['onclick' => 'checkSelect()']); $this->addContactTypeSelector(); } @@ -68,12 +68,12 @@ public function buildQuickForm() { * Process the uploaded file. */ public function postProcess() { - $this->storeFormValues(array( + $this->storeFormValues([ 'onDuplicate', 'contactType', 'dateFormats', 'savedMapping', - )); + ]); $this->submitFileForMapping('CRM_Contribute_Import_Parser_Contribution'); } diff --git a/CRM/Contribute/Import/Form/MapField.php b/CRM/Contribute/Import/Form/MapField.php index 8421712d4d21..14d8c0805e06 100644 --- a/CRM/Contribute/Import/Form/MapField.php +++ b/CRM/Contribute/Import/Form/MapField.php @@ -1,9 +1,9 @@ $contactORContributionId == 'contribution_id' ? ts('Contribution ID') : ts('Contact ID'), + 'total_amount' => ts('Total Amount'), + 'financial_type' => ts('Financial Type'), + ]; + + foreach ($requiredFields as $field => $title) { + if (!in_array($field, $importKeys)) { + if (empty($errors['_qf_default'])) { + $errors['_qf_default'] = ''; + } + if ($field == $contactORContributionId) { + if (!($weightSum >= $threshold || in_array('external_identifier', $importKeys)) && + $self->_onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE + ) { + $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $fieldMessage " . ts('(Sum of all weights should be greater than or equal to threshold: %1).', [1 => $threshold]) . '
    '; + } + elseif ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && + !(in_array('invoice_id', $importKeys) || in_array('trxn_id', $importKeys) || + in_array('contribution_id', $importKeys) + ) + ) { + $errors['_qf_default'] .= ts('Invoice ID or Transaction ID or Contribution ID are required to match to the existing contribution records in Update mode.') . '
    '; + } + } + else { + $errors['_qf_default'] .= ts('Missing required field: %1', [1 => $title]) . '
    '; + } + } + } + return $errors; + } /** * Set variables up before form is built. @@ -55,41 +102,47 @@ public function preProcess() { if ($skipColumnHeader) { $this->assign('skipColumnHeader', $skipColumnHeader); $this->assign('rowDisplayCount', 3); - /* if we had a column header to skip, stash it for later */ + // If we had a column header to skip, stash it for later $this->_columnHeaders = $this->_dataValues[0]; } else { $this->assign('rowDisplayCount', 2); } - $highlightedFields = array('financial_type', 'total_amount'); + $highlightedFields = ['financial_type', 'total_amount']; //CRM-2219 removing other required fields since for updation only //invoice id or trxn id or contribution id is required. if ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { - $remove = array('contribution_contact_id', 'email', 'first_name', 'last_name', 'external_identifier'); + $remove = [ + 'contribution_contact_id', + 'email', + 'first_name', + 'last_name', + 'external_identifier', + ]; foreach ($remove as $value) { unset($this->_mapperFields[$value]); } //modify field title only for update mode. CRM-3245 - foreach (array( - 'contribution_id', - 'invoice_id', - 'trxn_id', - ) as $key) { + foreach ([ + 'contribution_id', + 'invoice_id', + 'trxn_id', + ] as $key) { $this->_mapperFields[$key] .= ' (match to contribution record)'; $highlightedFields[] = $key; } } elseif ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) { unset($this->_mapperFields['contribution_id']); - $highlightedFieldsArray = array( + $highlightedFieldsArray = [ 'contribution_contact_id', 'email', 'first_name', 'last_name', 'external_identifier', - ); + ]; foreach ($highlightedFieldsArray as $name) { $highlightedFields[] = $name; } @@ -103,60 +156,21 @@ public function preProcess() { /** * Build the form object. + * + * @throws \CiviCRM_API3_Exception */ public function buildQuickForm() { - //to save the current mappings - if (!$this->get('savedMapping')) { - $saveDetailsName = ts('Save this field mapping'); - $this->applyFilter('saveMappingName', 'trim'); - $this->add('text', 'saveMappingName', ts('Name')); - $this->add('text', 'saveMappingDesc', ts('Description')); - } - else { - $savedMapping = $this->get('savedMapping'); + $savedMappingID = $this->get('savedMapping'); + $this->buildSavedMappingFields($savedMappingID); - list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingRelation) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping); - - $mappingName = $mappingName[1]; - $mappingContactType = $mappingContactType[1]; - $mappingLocation = CRM_Utils_Array::value('1', CRM_Utils_Array::value(1, $mappingLocation)); - $mappingPhoneType = CRM_Utils_Array::value('1', CRM_Utils_Array::value(1, $mappingPhoneType)); - $mappingRelation = CRM_Utils_Array::value('1', CRM_Utils_Array::value(1, $mappingRelation)); - - //mapping is to be loaded from database - - $params = array('id' => $savedMapping); - $temp = array(); - $mappingDetails = CRM_Core_BAO_Mapping::retrieve($params, $temp); - - $this->assign('loadedMapping', $mappingDetails->name); - $this->set('loadedMapping', $savedMapping); - - $getMappingName = new CRM_Core_DAO_Mapping(); - $getMappingName->id = $savedMapping; - $getMappingName->mapping_type = 'Import Contributions'; - $getMappingName->find(); - while ($getMappingName->fetch()) { - $mapperName = $getMappingName->name; - } - - $this->assign('savedName', $mapperName); - - $this->add('hidden', 'mappingId', $savedMapping); - - $this->addElement('checkbox', 'updateMapping', ts('Update this field mapping'), NULL); - $saveDetailsName = ts('Save as a new field mapping'); - $this->add('text', 'saveMappingName', ts('Name')); - $this->add('text', 'saveMappingDesc', ts('Description')); - } - - $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, array('onclick' => "showSaveDetails(this)")); - - $this->addFormRule(array('CRM_Contribute_Import_Form_MapField', 'formRule'), $this); + $this->addFormRule([ + 'CRM_Contribute_Import_Form_MapField', + 'formRule', + ], $this); //-------- end of saved mapping stuff --------- - $defaults = array(); + $defaults = []; $mapperKeys = array_keys($this->_mapperFields); $hasHeaders = !empty($this->_columnHeaders); $headerPatterns = $this->get('headerPatterns'); @@ -191,9 +205,13 @@ public function buildQuickForm() { $warning = 0; for ($i = 0; $i < $this->_columnCount; $i++) { - $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', array(1 => $i)), NULL); + $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL); $jsSet = FALSE; if ($this->get('savedMapping')) { + list($mappingName, $mappingContactType) = CRM_Core_BAO_Mapping::getMappingFields($savedMappingID); + + $mappingName = $mappingName[1]; + $mappingContactType = $mappingContactType[1]; if (isset($mappingName[$i])) { if ($mappingName[$i] != ts('- do not import -')) { @@ -207,16 +225,16 @@ public function buildQuickForm() { $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n"; $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n"; - $defaults["mapper[$i]"] = array( + $defaults["mapper[$i]"] = [ CRM_Utils_Array::value(0, $mappingHeader), ($softField) ? $softField : "", - (isset($locationId)) ? $locationId : "", - (isset($phoneType)) ? $phoneType : "", - ); + "", + "", + ]; $jsSet = TRUE; } else { - $defaults["mapper[$i]"] = array(); + $defaults["mapper[$i]"] = []; } if (!$jsSet) { for ($k = 1; $k < 4; $k++) { @@ -229,10 +247,10 @@ public function buildQuickForm() { $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n"; if ($hasHeaders) { - $defaults["mapper[$i]"] = array($this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns)); + $defaults["mapper[$i]"] = [$this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns)]; } else { - $defaults["mapper[$i]"] = array($this->defaultFromData($dataPatterns, $i)); + $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i)]; } } //end of load mapping @@ -248,28 +266,25 @@ public function buildQuickForm() { } else { // Infer the default from the column names if we have them - $defaults["mapper[$i]"] = array( - $this->defaultFromHeader($this->_columnHeaders[$i], - $headerPatterns - ), + $defaults["mapper[$i]"] = [ + $this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns), 0, - ); + ]; } } else { // Otherwise guess the default from the form of the data - $defaults["mapper[$i]"] = array( + $defaults["mapper[$i]"] = [ $this->defaultFromData($dataPatterns, $i), - // $defaultLocationType->id 0, - ); + ]; } if (!empty($mapperKeysValues) && $mapperKeysValues[$i][0] == 'soft_credit') { $js .= "cj('#mapper_" . $i . "_1').val($mapperKeysValues[$i][1]);\n"; $js .= "cj('#mapper_" . $i . "_2').val($mapperKeysValues[$i][2]);\n"; } } - $sel->setOptions(array($sel1, $sel2, $sel3, $sel4)); + $sel->setOptions([$sel1, $sel2, $sel3, $sel4]); } $js .= "\n"; $this->assign('initHideBoxes', $js); @@ -291,23 +306,22 @@ public function buildQuickForm() { $this->setDefaults($defaults); - $this->addButtons(array( - array( - 'type' => 'back', - 'name' => ts('Previous'), - ), - array( - 'type' => 'next', - 'name' => ts('Continue'), - 'spacing' => '          ', - 'isDefault' => TRUE, - ), - array( - 'type' => 'cancel', - 'name' => ts('Cancel'), - ), - ) - ); + $this->addButtons([ + [ + 'type' => 'back', + 'name' => ts('Previous'), + ], + [ + 'type' => 'next', + 'name' => ts('Continue'), + 'spacing' => '          ', + 'isDefault' => TRUE, + ], + [ + 'type' => 'cancel', + 'name' => ts('Cancel'), + ], + ]); } /** @@ -323,25 +337,25 @@ public function buildQuickForm() { * list of errors to be posted back to the form */ public static function formRule($fields, $files, $self) { - $errors = array(); + $errors = []; $fieldMessage = NULL; $contactORContributionId = $self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE ? 'contribution_id' : 'contribution_contact_id'; if (!array_key_exists('savedMapping', $fields)) { - $importKeys = array(); + $importKeys = []; foreach ($fields['mapper'] as $mapperPart) { $importKeys[] = $mapperPart[0]; } $contactTypeId = $self->get('contactType'); - $contactTypes = array( + $contactTypes = [ CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual', CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household', CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization', - ); - $params = array( + ]; + $params = [ 'used' => 'Unsupervised', 'contact_type' => isset($contactTypes[$contactTypeId]) ? $contactTypes[$contactTypeId] : '', - ); + ]; list($ruleFields, $threshold) = CRM_Dedupe_BAO_RuleGroup::dedupeRuleFieldsWeight($params); $weightSum = 0; foreach ($importKeys as $key => $val) { @@ -361,49 +375,19 @@ public static function formRule($fields, $files, $self) { foreach ($ruleFields as $field => $weight) { $fieldMessage .= ' ' . $field . '(weight ' . $weight . ')'; } - - // FIXME: should use the schema titles, not redeclare them - $requiredFields = array( - $contactORContributionId == 'contribution_id' ? 'contribution_id' : 'contribution_contact_id' => $contactORContributionId == 'contribution_id' ? ts('Contribution ID') : ts('Contact ID'), - 'total_amount' => ts('Total Amount'), - 'financial_type' => ts('Financial Type'), - ); - - foreach ($requiredFields as $field => $title) { - if (!in_array($field, $importKeys)) { - if (empty($errors['_qf_default'])) { - $errors['_qf_default'] = ''; - } - if ($field == $contactORContributionId) { - if (!($weightSum >= $threshold || in_array('external_identifier', $importKeys)) && - $self->_onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE - ) { - $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $fieldMessage " . ts('(Sum of all weights should be greater than or equal to threshold: %1).', array( - 1 => $threshold, - )) . '
    '; - } - elseif ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && - !(in_array('invoice_id', $importKeys) || in_array('trxn_id', $importKeys) || - in_array('contribution_id', $importKeys) - ) - ) { - $errors['_qf_default'] .= ts('Invoice ID or Transaction ID or Contribution ID are required to match to the existing contribution records in Update mode.') . '
    '; - } - } - else { - $errors['_qf_default'] .= ts('Missing required field: %1', array( - 1 => $title, - )) . '
    '; - } - } - } + $errors = self::checkRequiredFields($self, $contactORContributionId, $importKeys, $errors, $weightSum, $threshold, $fieldMessage); //at least one field should be mapped during update. if ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { $atleastOne = FALSE; foreach ($self->_mapperFields as $key => $field) { if (in_array($key, $importKeys) && - !in_array($key, array('doNotImport', 'contribution_id', 'invoice_id', 'trxn_id')) + !in_array($key, [ + 'doNotImport', + 'contribution_id', + 'invoice_id', + 'trxn_id', + ]) ) { $atleastOne = TRUE; break; @@ -421,8 +405,7 @@ public static function formRule($fields, $files, $self) { $errors['saveMappingName'] = ts('Name is required to save Import Mapping'); } else { - $mappingTypeId = CRM_Core_OptionGroup::getValue('mapping_type', 'Import Contribution', 'name'); - if (CRM_Core_BAO_Mapping::checkMapping($nameField, $mappingTypeId)) { + if (CRM_Core_BAO_Mapping::checkMapping($nameField, CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Contribution'))) { $errors['saveMappingName'] = ts('Duplicate Import Contribution Mapping Name'); } } @@ -460,7 +443,7 @@ public function postProcess() { $seperator = $this->controller->exportValue('DataSource', 'fieldSeparator'); $skipColumnHeader = $this->controller->exportValue('DataSource', 'skipColumnHeader'); - $mapper = $mapperKeys = $mapperKeysMain = $mapperSoftCredit = $softCreditFields = $mapperPhoneType = $mapperSoftCreditType = array(); + $mapper = $mapperKeys = $mapperKeysMain = $mapperSoftCredit = $softCreditFields = $mapperPhoneType = $mapperSoftCreditType = []; $mapperKeys = $this->controller->exportValue($this->_name, 'mapper'); $softCreditTypes = CRM_Core_OptionGroup::values('soft_credit_type'); @@ -478,10 +461,10 @@ public function postProcess() { else { $softCreditFields[$i] = $mapperSoftCredit[$i]; } - $mapperSoftCreditType[$i] = array( + $mapperSoftCreditType[$i] = [ 'value' => isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : '', 'label' => isset($softCreditTypes[$mapperKeys[$i][2]]) ? $softCreditTypes[$mapperKeys[$i][2]] : '', - ); + ]; } else { $mapperSoftCredit[$i] = $softCreditFields[$i] = $mapperSoftCreditType[$i] = NULL; @@ -501,7 +484,7 @@ public function postProcess() { $mappingFields->mapping_id = $params['mappingId']; $mappingFields->find(); - $mappingFieldsId = array(); + $mappingFieldsId = []; while ($mappingFields->fetch()) { if ($mappingFields->id) { $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id; @@ -523,14 +506,11 @@ public function postProcess() { //Saving Mapping Details and Records if (!empty($params['saveMapping'])) { - $mappingParams = array( + $mappingParams = [ 'name' => $params['saveMappingName'], 'description' => $params['saveMappingDesc'], - 'mapping_type_id' => CRM_Core_OptionGroup::getValue('mapping_type', - 'Import Contribution', - 'name' - ), - ); + 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Contribution'), + ]; $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams); for ($i = 0; $i < $this->_columnCount; $i++) { diff --git a/CRM/Contribute/Import/Form/Preview.php b/CRM/Contribute/Import/Form/Preview.php index c5bdf920c93a..074c06b6d5f6 100644 --- a/CRM/Contribute/Import/Form/Preview.php +++ b/CRM/Contribute/Import/Form/Preview.php @@ -1,9 +1,9 @@ set('downloadMismatchRecordsUrl', CRM_Utils_System::url('civicrm/export', $urlParams)); } - $properties = array( + $properties = [ 'mapper', 'softCreditFields', 'mapperSoftCreditType', @@ -97,7 +97,8 @@ public function preProcess() { 'downloadErrorRecordsUrl', 'downloadConflictRecordsUrl', 'downloadMismatchRecordsUrl', - ); + ]; + $this->setStatusUrl(); foreach ($properties as $property) { $this->assign($property, $this->get($property)); @@ -117,9 +118,9 @@ public function postProcess() { $mapperSoftCreditType = $this->get('mapperSoftCreditType'); $mapper = $this->controller->exportValue('MapField', 'mapper'); - $mapperKeys = array(); - $mapperSoftCredit = array(); - $mapperPhoneType = array(); + $mapperKeys = []; + $mapperSoftCredit = []; + $mapperPhoneType = []; foreach ($mapper as $key => $value) { $mapperKeys[$key] = $mapper[$key][0]; @@ -137,7 +138,7 @@ public function postProcess() { $mapFields = $this->get('fields'); foreach ($mapper as $key => $value) { - $header = array(); + $header = []; if (isset($mapFields[$mapper[$key][0]])) { $header[] = $mapFields[$mapper[$key][0]]; } @@ -148,7 +149,9 @@ public function postProcess() { $skipColumnHeader, CRM_Import_Parser::MODE_IMPORT, $this->get('contactType'), - $onDuplicate + $onDuplicate, + $this->get('statusID'), + $this->get('totalRowCount') ); // Add all the necessary variables to the form. @@ -158,7 +161,7 @@ public function postProcess() { $errorStack = CRM_Core_Error::singleton(); $errors = $errorStack->getErrors(); - $errorMessage = array(); + $errorMessage = []; if (is_array($errors)) { foreach ($errors as $key => $value) { diff --git a/CRM/Contribute/Import/Form/Summary.php b/CRM/Contribute/Import/Form/Summary.php index d0b20cd5af37..ddd0222305d4 100644 --- a/CRM/Contribute/Import/Form/Summary.php +++ b/CRM/Contribute/Import/Form/Summary.php @@ -1,9 +1,9 @@ assign('dupeActionString', $dupeActionString); - $properties = array( + $properties = [ 'totalRowCount', 'validRowCount', 'invalidRowCount', @@ -119,7 +119,7 @@ public function preProcess() { 'invalidPledgePaymentRowCount', 'downloadPledgePaymentErrorRecordsUrl', 'downloadSoftCreditErrorRecordsUrl', - ); + ]; foreach ($properties as $property) { $this->assign($property, $this->get($property)); } diff --git a/CRM/Contribute/Import/Parser.php b/CRM/Contribute/Import/Parser.php index 46b841f259bf..24c30758c8ba 100644 --- a/CRM/Contribute/Import/Parser.php +++ b/CRM/Contribute/Import/Parser.php @@ -1,9 +1,9 @@ _invalidRowCount = $this->_validCount = $this->_invalidSoftCreditRowCount = $this->_invalidPledgePaymentRowCount = 0; $this->_totalCount = $this->_conflictCount = 0; - $this->_errors = array(); - $this->_warnings = array(); - $this->_conflicts = array(); - $this->_pledgePaymentErrors = array(); - $this->_softCreditErrors = array(); + $this->_errors = []; + $this->_warnings = []; + $this->_conflicts = []; + $this->_pledgePaymentErrors = []; + $this->_softCreditErrors = []; + if ($statusID) { + $this->progressImport($statusID); + $startTimestamp = $currTimestamp = $prevTimestamp = time(); + } $this->_fileSize = number_format(filesize($fileName) / 1024.0, 2); if ($mode == self::MODE_MAPFIELD) { - $this->_rows = array(); + $this->_rows = []; } else { $this->_activeFieldCount = count($this->_activeFields); @@ -215,6 +235,9 @@ public function run( } elseif ($mode == self::MODE_IMPORT) { $returnCode = $this->import($onDuplicate, $values); + if ($statusID && (($this->_lineCount % 50) == 0)) { + $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount); + } } else { $returnCode = self::ERROR; @@ -256,38 +279,32 @@ public function run( if ($returnCode == self::ERROR) { $this->_invalidRowCount++; - if ($this->_invalidRowCount < $this->_maxErrorCount) { - $recordNumber = $this->_lineCount; - if ($this->_haveColumnHeader) { - $recordNumber--; - } - array_unshift($values, $recordNumber); - $this->_errors[] = $values; + $recordNumber = $this->_lineCount; + if ($this->_haveColumnHeader) { + $recordNumber--; } + array_unshift($values, $recordNumber); + $this->_errors[] = $values; } if ($returnCode == self::PLEDGE_PAYMENT_ERROR) { $this->_invalidPledgePaymentRowCount++; - if ($this->_invalidPledgePaymentRowCount < $this->_maxErrorCount) { - $recordNumber = $this->_lineCount; - if ($this->_haveColumnHeader) { - $recordNumber--; - } - array_unshift($values, $recordNumber); - $this->_pledgePaymentErrors[] = $values; + $recordNumber = $this->_lineCount; + if ($this->_haveColumnHeader) { + $recordNumber--; } + array_unshift($values, $recordNumber); + $this->_pledgePaymentErrors[] = $values; } if ($returnCode == self::SOFT_CREDIT_ERROR) { $this->_invalidSoftCreditRowCount++; - if ($this->_invalidSoftCreditRowCount < $this->_maxErrorCount) { - $recordNumber = $this->_lineCount; - if ($this->_haveColumnHeader) { - $recordNumber--; - } - array_unshift($values, $recordNumber); - $this->_softCreditErrors[] = $values; + $recordNumber = $this->_lineCount; + if ($this->_haveColumnHeader) { + $recordNumber--; } + array_unshift($values, $recordNumber); + $this->_softCreditErrors[] = $values; } if ($returnCode == self::CONFLICT) { @@ -342,57 +359,47 @@ public function run( } if ($this->_invalidRowCount) { // removed view url for invlaid contacts - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_errorFileName = self::errorFileName(self::ERROR); self::exportCSV($this->_errorFileName, $headers, $this->_errors); } if ($this->_invalidPledgePaymentRowCount) { // removed view url for invlaid contacts - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_pledgePaymentErrorsFileName = self::errorFileName(self::PLEDGE_PAYMENT_ERROR); self::exportCSV($this->_pledgePaymentErrorsFileName, $headers, $this->_pledgePaymentErrors); } if ($this->_invalidSoftCreditRowCount) { // removed view url for invlaid contacts - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_softCreditErrorsFileName = self::errorFileName(self::SOFT_CREDIT_ERROR); self::exportCSV($this->_softCreditErrorsFileName, $headers, $this->_softCreditErrors); } if ($this->_conflictCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('Reason'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('Reason'), + ], $customHeaders); $this->_conflictFileName = self::errorFileName(self::CONFLICT); self::exportCSV($this->_conflictFileName, $headers, $this->_conflicts); } if ($this->_duplicateCount) { - $headers = array_merge(array( - ts('Line Number'), - ts('View Contribution URL'), - ), - $customHeaders - ); + $headers = array_merge([ + ts('Line Number'), + ts('View Contribution URL'), + ], $customHeaders); $this->_duplicateFileName = self::errorFileName(self::DUPLICATE); self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates); @@ -420,20 +427,34 @@ public function setActiveFields($fieldKeys) { } /** + * Store the soft credit field information. + * + * This was perhaps done this way on the believe that a lot of code pain + * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise + * readability & maintainability next since we can just work with functions to retrieve + * data from the metadata. + * * @param array $elements */ public function setActiveFieldSoftCredit($elements) { - for ($i = 0; $i < count($elements); $i++) { - $this->_activeFields[$i]->_softCreditField = $elements[$i]; + foreach ((array) $elements as $i => $element) { + $this->_activeFields[$i]->_softCreditField = $element; } } /** + * Store the soft credit field type information. + * + * This was perhaps done this way on the believe that a lot of code pain + * was worth it to avoid negligible-cost array iterations. Perhaps we could prioritise + * readability & maintainability next since we can just work with functions to retrieve + * data from the metadata. + * * @param array $elements */ public function setActiveFieldSoftCreditType($elements) { - for ($i = 0; $i < count($elements); $i++) { - $this->_activeFields[$i]->_softCreditType = $elements[$i]; + foreach ((array) $elements as $i => $element) { + $this->_activeFields[$i]->_softCreditType = $element; } } @@ -444,12 +465,12 @@ public function setActiveFieldSoftCreditType($elements) { * (reference ) associative array of name/value pairs */ public function &getActiveFieldParams() { - $params = array(); + $params = []; for ($i = 0; $i < $this->_activeFieldCount; $i++) { if (isset($this->_activeFields[$i]->_value)) { if (isset($this->_activeFields[$i]->_softCreditField)) { if (!isset($params[$this->_activeFields[$i]->_name])) { - $params[$this->_activeFields[$i]->_name] = array(); + $params[$this->_activeFields[$i]->_name] = []; } $params[$this->_activeFields[$i]->_name][$i][$this->_activeFields[$i]->_softCreditField] = $this->_activeFields[$i]->_value; if (isset($this->_activeFields[$i]->_softCreditType)) { @@ -565,7 +586,7 @@ public function set($store, $mode = self::MODE_SUMMARY) { * @param array $data */ public static function exportCSV($fileName, $header, $data) { - $output = array(); + $output = []; $fd = fopen($fileName, 'w'); foreach ($header as $key => $value) { diff --git a/CRM/Contribute/Import/Parser/Contribution.php b/CRM/Contribute/Import/Parser/Contribution.php index c7529b517d37..49cb0522e83a 100644 --- a/CRM/Contribute/Import/Parser/Contribution.php +++ b/CRM/Contribute/Import/Parser/Contribution.php @@ -1,9 +1,9 @@ _mapperKeys = &$mapperKeys; $this->_mapperSoftCredit = &$mapperSoftCredit; @@ -74,27 +74,27 @@ public function init() { $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE); $fields = array_merge($fields, - array( - 'soft_credit' => array( + [ + 'soft_credit' => [ 'title' => ts('Soft Credit'), 'softCredit' => TRUE, 'headerPattern' => '/Soft Credit/i', - ), - ) + ], + ] ); // add pledge fields only if its is enabled if (CRM_Core_Permission::access('CiviPledge')) { - $pledgeFields = array( - 'pledge_payment' => array( + $pledgeFields = [ + 'pledge_payment' => [ 'title' => ts('Pledge Payment'), 'headerPattern' => '/Pledge Payment/i', - ), - 'pledge_id' => array( + ], + 'pledge_id' => [ 'title' => ts('Pledge ID'), 'headerPattern' => '/Pledge ID/i', - ), - ); + ], + ]; $fields = array_merge($fields, $pledgeFields); } @@ -105,7 +105,7 @@ public function init() { $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']); } - $this->_newContributions = array(); + $this->_newContributions = []; $this->setActiveFields($this->_mapperKeys); $this->setActiveFieldSoftCredit($this->_mapperSoftCredit); @@ -177,49 +177,7 @@ public function summary(&$values) { $errorMessage = NULL; //for date-Formats - $session = CRM_Core_Session::singleton(); - $dateType = $session->get('dateTypes'); - foreach ($params as $key => $val) { - if ($val) { - switch ($key) { - case 'receive_date': - if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { - $params[$key] = $dateValue; - } - else { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receive Date', $errorMessage); - } - break; - - case 'cancel_date': - if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { - $params[$key] = $dateValue; - } - else { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Cancel Date', $errorMessage); - } - break; - - case 'receipt_date': - if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { - $params[$key] = $dateValue; - } - else { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receipt date', $errorMessage); - } - break; - - case 'thankyou_date': - if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { - $params[$key] = $dateValue; - } - else { - CRM_Contact_Import_Parser_Contact::addToErrorMsg('Thankyou Date', $errorMessage); - } - break; - } - } - } + $errorMessage = implode('; ', $this->formatDateFields($params)); //date-Format part ends $params['contact_type'] = 'Contribution'; @@ -256,49 +214,13 @@ public function import($onDuplicate, &$values) { } $params = &$this->getActiveFieldParams(); - $formatted = array('version' => 3); - - // don't add to recent items, CRM-4399 - $formatted['skipRecentView'] = TRUE; - - //for date-Formats - $session = CRM_Core_Session::singleton(); - $dateType = $session->get('dateTypes'); - - $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Contribution'; - $customFields = CRM_Core_BAO_CustomField::getFields($customDataType); + $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE]; //CRM-10994 if (isset($params['total_amount']) && $params['total_amount'] == 0) { $params['total_amount'] = '0.00'; } - foreach ($params as $key => $val) { - if ($val) { - switch ($key) { - case 'receive_date': - case 'cancel_date': - case 'receipt_date': - case 'thankyou_date': - $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType); - break; - - case 'pledge_payment': - $params[$key] = CRM_Utils_String::strtobool($val); - break; - - } - if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { - if ($customFields[$customFieldID]['data_type'] == 'Date') { - CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key); - unset($params[$key]); - } - elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { - $params[$key] = CRM_Utils_String::strtoboolstr($val); - } - } - } - } - //date-Format part ends + $this->formatInput($params, $formatted); static $indieFields = NULL; if ($indieFields == NULL) { @@ -306,7 +228,7 @@ public function import($onDuplicate, &$values) { $indieFields = $tempIndieFields; } - $paramValues = array(); + $paramValues = []; foreach ($params as $key => $field) { if ($field == NULL || $field === '') { continue; @@ -336,8 +258,7 @@ public function import($onDuplicate, &$values) { if (!empty($paramValues['pledge_payment'])) { $paramValues['onDuplicate'] = $onDuplicate; } - require_once 'CRM/Utils/DeprecatedUtils.php'; - $formatError = _civicrm_api3_deprecated_formatted_param($paramValues, $formatted, TRUE, $onDuplicate); + $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate); if ($formatError) { array_unshift($values, $formatError['error_message']); @@ -360,11 +281,11 @@ public function import($onDuplicate, &$values) { //fix for CRM-2219 - Update Contribution // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) { - $dupeIds = array( + $dupeIds = [ 'id' => CRM_Utils_Array::value('contribution_id', $paramValues), 'trxn_id' => CRM_Utils_Array::value('trxn_id', $paramValues), 'invoice_id' => CRM_Utils_Array::value('invoice_id', $paramValues), - ); + ]; $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds); @@ -376,7 +297,7 @@ public function import($onDuplicate, &$values) { ); //process note if (!empty($paramValues['note'])) { - $noteID = array(); + $noteID = []; $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id'); $daoNote = new CRM_Core_BAO_Note(); $daoNote->entity_table = 'civicrm_contribution'; @@ -385,32 +306,32 @@ public function import($onDuplicate, &$values) { $noteID['id'] = $daoNote->id; } - $noteParams = array( + $noteParams = [ 'entity_table' => 'civicrm_contribution', 'note' => $paramValues['note'], 'entity_id' => $ids['contribution'], 'contact_id' => $contactID, - ); + ]; CRM_Core_BAO_Note::add($noteParams, $noteID); unset($formatted['note']); } //need to check existing soft credit contribution, CRM-3968 if (!empty($formatted['soft_credit'])) { - $dupeSoftCredit = array( + $dupeSoftCredit = [ 'contact_id' => $formatted['soft_credit'], 'contribution_id' => $ids['contribution'], - ); + ]; //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']); if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) { foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) { if (!empty($existingSoftCreditValues['soft_credit_id'])) { - civicrm_api3('ContributionSoft', 'delete', array( + civicrm_api3('ContributionSoft', 'delete', [ 'id' => $existingSoftCreditValues['soft_credit_id'], 'pcp_id' => NULL, - )); + ]); } } } @@ -430,11 +351,11 @@ public function import($onDuplicate, &$values) { return CRM_Import_Parser::VALID; } else { - $labels = array( + $labels = [ 'id' => 'Contribution ID', 'trxn_id' => 'Transaction ID', 'invoice_id' => 'Invoice ID', - ); + ]; foreach ($dupeIds as $k => $v) { if ($v) { $errorMsg[] = "$labels[$k] $v"; @@ -453,10 +374,7 @@ public function import($onDuplicate, &$values) { $paramValues['contact_type'] = $this->_contactType; } - $paramValues['version'] = 3; - //retrieve contact id using contact dedupe rule - require_once 'CRM/Utils/DeprecatedUtils.php'; - $error = _civicrm_api3_deprecated_check_contact_dedupe($paramValues); + $error = $this->checkContactDuplicate($paramValues); if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) { $matchedIDs = explode(',', $error['error_message']['params'][0]); @@ -498,10 +416,10 @@ public function import($onDuplicate, &$values) { } else { // Using new Dedupe rule. - $ruleParams = array( + $ruleParams = [ 'contact_type' => $this->_contactType, 'used' => 'Unsupervised', - ); + ]; $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams); $disp = NULL; foreach ($fieldsArray as $value) { @@ -578,7 +496,7 @@ public function import($onDuplicate, &$values) { public function processPledgePayments(&$formatted) { if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) { //get completed status - $completeStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name'); + $completeStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); //need to update payment record to map contribution_id CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'], @@ -586,7 +504,7 @@ public function processPledgePayments(&$formatted) { ); CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($formatted['pledge_id'], - array($formatted['pledge_payment_id']), + [$formatted['pledge_payment_id']], $completeStatusID, NULL, $formatted['total_amount'] @@ -611,4 +529,513 @@ public function &getImportedContributions() { public function fini() { } + /** + * Format date fields from input to mysql. + * + * @param array $params + * + * @return array + * Error messages, if any. + */ + public function formatDateFields(&$params) { + $errorMessage = []; + $dateType = CRM_Core_Session::singleton()->get('dateTypes'); + foreach ($params as $key => $val) { + if ($val) { + switch ($key) { + case 'receive_date': + if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { + $params[$key] = $dateValue; + } + else { + $errorMessage[] = ts('Receive Date'); + } + break; + + case 'cancel_date': + if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { + $params[$key] = $dateValue; + } + else { + $errorMessage[] = ts('Cancel Date'); + } + break; + + case 'receipt_date': + if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { + $params[$key] = $dateValue; + } + else { + $errorMessage[] = ts('Receipt date'); + } + break; + + case 'thankyou_date': + if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { + $params[$key] = $dateValue; + } + else { + $errorMessage[] = ts('Thankyou Date'); + } + break; + } + } + } + return $errorMessage; + } + + /** + * Format input params to suit api handling. + * + * Over time all the parts of deprecatedFormatParams + * and all the parts of the import function on this class that relate to + * reformatting input should be moved here and tests should be added in + * CRM_Contribute_Import_Parser_ContributionTest. + * + * @param array $params + * @param array $formatted + */ + public function formatInput(&$params, &$formatted = []) { + $dateType = CRM_Core_Session::singleton()->get('dateTypes'); + $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Contribution'; + $customFields = CRM_Core_BAO_CustomField::getFields($customDataType); + // @todo call formatDateFields & move custom data handling there. + // Also note error handling for dates is currently in deprecatedFormatParams + // we should use the error handling in formatDateFields. + foreach ($params as $key => $val) { + // @todo - call formatDateFields instead. + if ($val) { + switch ($key) { + case 'receive_date': + case 'cancel_date': + case 'receipt_date': + case 'thankyou_date': + $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType); + break; + + case 'pledge_payment': + $params[$key] = CRM_Utils_String::strtobool($val); + break; + + } + if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { + if ($customFields[$customFieldID]['data_type'] == 'Date') { + CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key); + unset($params[$key]); + } + elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { + $params[$key] = CRM_Utils_String::strtoboolstr($val); + } + } + } + } + } + + /** + * take the input parameter list as specified in the data model and + * convert it into the same format that we use in QF and BAO object + * + * @param array $params + * Associative array of property name/value. + * pairs to insert in new contact. + * @param array $values + * The reformatted properties that we can use internally. + * ' + * + * @param bool $create + * @param null $onDuplicate + * + * @return array|CRM_Error + */ + private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) { + require_once 'CRM/Utils/DeprecatedUtils.php'; + // copy all the contribution fields as is + require_once 'api/v3/utils.php'; + $fields = CRM_Core_DAO::getExportableFieldsWithPseudoConstants('CRM_Contribute_BAO_Contribution'); + + _civicrm_api3_store_values($fields, $params, $values); + + $customFields = CRM_Core_BAO_CustomField::getFields('Contribution', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE); + + foreach ($params as $key => $value) { + // ignore empty values or empty arrays etc + if (CRM_Utils_System::isNull($value)) { + continue; + } + + // Handling Custom Data + if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { + $values[$key] = $value; + $type = $customFields[$customFieldID]['html_type']; + if ($type == 'CheckBox' || $type == 'Multi-Select') { + $mulValues = explode(',', $value); + $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); + $values[$key] = []; + foreach ($mulValues as $v1) { + foreach ($customOption as $customValueID => $customLabel) { + $customValue = $customLabel['value']; + if ((strtolower($customLabel['label']) == strtolower(trim($v1))) || + (strtolower($customValue) == strtolower(trim($v1))) + ) { + if ($type == 'CheckBox') { + $values[$key][$customValue] = 1; + } + else { + $values[$key][] = $customValue; + } + } + } + } + } + elseif ($type == 'Select' || $type == 'Radio' || + ($type == 'Autocomplete-Select' && + $customFields[$customFieldID]['data_type'] == 'String' + ) + ) { + $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); + foreach ($customOption as $customFldID => $customValue) { + $val = CRM_Utils_Array::value('value', $customValue); + $label = CRM_Utils_Array::value('label', $customValue); + $label = strtolower($label); + $value = strtolower(trim($value)); + if (($value == $label) || ($value == strtolower($val))) { + $values[$key] = $val; + } + } + } + } + + switch ($key) { + case 'contribution_contact_id': + if (!CRM_Utils_Rule::integer($value)) { + return civicrm_api3_create_error("contact_id not valid: $value"); + } + $dao = new CRM_Core_DAO(); + $qParams = []; + $svq = $dao->singleValueQuery("SELECT is_deleted FROM civicrm_contact WHERE id = $value", + $qParams + ); + if (!isset($svq)) { + return civicrm_api3_create_error("Invalid Contact ID: There is no contact record with contact_id = $value."); + } + elseif ($svq == 1) { + return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact."); + } + + $values['contact_id'] = $values['contribution_contact_id']; + unset($values['contribution_contact_id']); + break; + + case 'contact_type': + // import contribution record according to select contact type + require_once 'CRM/Contact/DAO/Contact.php'; + $contactType = new CRM_Contact_DAO_Contact(); + $contactId = CRM_Utils_Array::value('contribution_contact_id', $params); + $externalId = CRM_Utils_Array::value('external_identifier', $params); + $email = CRM_Utils_Array::value('email', $params); + //when insert mode check contact id or external identifier + if ($contactId || $externalId) { + $contactType->id = $contactId; + $contactType->external_identifier = $externalId; + if ($contactType->find(TRUE)) { + if ($params['contact_type'] != $contactType->contact_type) { + return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type"); + } + } + } + elseif ($email) { + if (!CRM_Utils_Rule::email($email)) { + return civicrm_api3_create_error("Invalid email address $email provided. Row was skipped"); + } + + // get the contact id from duplicate contact rule, if more than one contact is returned + // we should return error, since current interface allows only one-one mapping + $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']]; + $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams); + if (!$checkDedupe['is_error']) { + return civicrm_api3_create_error("Invalid email address(doesn't exist) $email. Row was skipped"); + } + else { + $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]); + if (count($matchingContactIds) > 1) { + return civicrm_api3_create_error("Invalid email address(duplicate) $email. Row was skipped"); + } + elseif (count($matchingContactIds) == 1) { + $params['contribution_contact_id'] = $matchingContactIds[0]; + } + } + } + elseif (!empty($params['contribution_id']) || !empty($params['trxn_id']) || !empty($params['invoice_id'])) { + // when update mode check contribution id or trxn id or + // invoice id + $contactId = new CRM_Contribute_DAO_Contribution(); + if (!empty($params['contribution_id'])) { + $contactId->id = $params['contribution_id']; + } + elseif (!empty($params['trxn_id'])) { + $contactId->trxn_id = $params['trxn_id']; + } + elseif (!empty($params['invoice_id'])) { + $contactId->invoice_id = $params['invoice_id']; + } + if ($contactId->find(TRUE)) { + $contactType->id = $contactId->contact_id; + if ($contactType->find(TRUE)) { + if ($params['contact_type'] != $contactType->contact_type) { + return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type"); + } + } + } + } + else { + if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { + return civicrm_api3_create_error("Empty Contribution and Invoice and Transaction ID. Row was skipped."); + } + } + break; + + case 'receive_date': + case 'cancel_date': + case 'receipt_date': + case 'thankyou_date': + if (!CRM_Utils_Rule::dateTime($value)) { + return civicrm_api3_create_error("$key not a valid date: $value"); + } + break; + + case 'non_deductible_amount': + case 'total_amount': + case 'fee_amount': + case 'net_amount': + // @todo add test like testPaymentTypeLabel & remove these lines as we can anticipate error will still be caught & handled. + if (!CRM_Utils_Rule::money($value)) { + return civicrm_api3_create_error("$key not a valid amount: $value"); + } + break; + + case 'currency': + if (!CRM_Utils_Rule::currencyCode($value)) { + return civicrm_api3_create_error("currency not a valid code: $value"); + } + break; + + case 'financial_type': + // @todo add test like testPaymentTypeLabel & remove these lines in favour of 'default' part of switch. + require_once 'CRM/Contribute/PseudoConstant.php'; + $contriTypes = CRM_Contribute_PseudoConstant::financialType(); + foreach ($contriTypes as $val => $type) { + if (strtolower($value) == strtolower($type)) { + $values['financial_type_id'] = $val; + break; + } + } + if (empty($values['financial_type_id'])) { + return civicrm_api3_create_error("Financial Type is not valid: $value"); + } + break; + + case 'soft_credit': + // import contribution record according to select contact type + // validate contact id and external identifier. + $value[$key] = $mismatchContactType = $softCreditContactIds = ''; + if (isset($params[$key]) && is_array($params[$key])) { + foreach ($params[$key] as $softKey => $softParam) { + $contactId = CRM_Utils_Array::value('contact_id', $softParam); + $externalId = CRM_Utils_Array::value('external_identifier', $softParam); + $email = CRM_Utils_Array::value('email', $softParam); + if ($contactId || $externalId) { + require_once 'CRM/Contact/DAO/Contact.php'; + $contact = new CRM_Contact_DAO_Contact(); + $contact->id = $contactId; + $contact->external_identifier = $externalId; + $errorMsg = NULL; + if (!$contact->find(TRUE)) { + $field = $contactId ? ts('Contact ID') : ts('External ID'); + $errorMsg = ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.", + [1 => $field, 2 => $contactId ? $contactId : $externalId]); + } + + if ($errorMsg) { + return civicrm_api3_create_error($errorMsg); + } + + // finally get soft credit contact id. + $values[$key][$softKey] = $softParam; + $values[$key][$softKey]['contact_id'] = $contact->id; + } + elseif ($email) { + if (!CRM_Utils_Rule::email($email)) { + return civicrm_api3_create_error("Invalid email address $email provided for Soft Credit. Row was skipped"); + } + + // get the contact id from duplicate contact rule, if more than one contact is returned + // we should return error, since current interface allows only one-one mapping + $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']]; + $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams); + if (!$checkDedupe['is_error']) { + return civicrm_api3_create_error("Invalid email address(doesn't exist) $email for Soft Credit. Row was skipped"); + } + else { + $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]); + if (count($matchingContactIds) > 1) { + return civicrm_api3_create_error("Invalid email address(duplicate) $email for Soft Credit. Row was skipped"); + } + elseif (count($matchingContactIds) == 1) { + $contactId = $matchingContactIds[0]; + unset($softParam['email']); + $values[$key][$softKey] = $softParam + ['contact_id' => $contactId]; + } + } + } + } + } + break; + + case 'pledge_payment': + case 'pledge_id': + + // giving respect to pledge_payment flag. + if (empty($params['pledge_payment'])) { + break; + } + + // get total amount of from import fields + $totalAmount = CRM_Utils_Array::value('total_amount', $params); + + $onDuplicate = CRM_Utils_Array::value('onDuplicate', $params); + + // we need to get contact id $contributionContactID to + // retrieve pledge details as well as to validate pledge ID + + // first need to check for update mode + if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && + ($params['contribution_id'] || $params['trxn_id'] || $params['invoice_id']) + ) { + $contribution = new CRM_Contribute_DAO_Contribution(); + if ($params['contribution_id']) { + $contribution->id = $params['contribution_id']; + } + elseif ($params['trxn_id']) { + $contribution->trxn_id = $params['trxn_id']; + } + elseif ($params['invoice_id']) { + $contribution->invoice_id = $params['invoice_id']; + } + + if ($contribution->find(TRUE)) { + $contributionContactID = $contribution->contact_id; + if (!$totalAmount) { + $totalAmount = $contribution->total_amount; + } + } + else { + return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.'); + } + } + else { + // first get the contact id for given contribution record. + if (!empty($params['contribution_contact_id'])) { + $contributionContactID = $params['contribution_contact_id']; + } + elseif (!empty($params['external_identifier'])) { + require_once 'CRM/Contact/DAO/Contact.php'; + $contact = new CRM_Contact_DAO_Contact(); + $contact->external_identifier = $params['external_identifier']; + if ($contact->find(TRUE)) { + $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $contact->id; + } + else { + return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.'); + } + } + else { + // we need to get contribution contact using de dupe + $error = _civicrm_api3_deprecated_check_contact_dedupe($params); + + if (isset($error['error_message']['params'][0])) { + $matchedIDs = explode(',', $error['error_message']['params'][0]); + + // check if only one contact is found + if (count($matchedIDs) > 1) { + return civicrm_api3_create_error($error['error_message']['message']); + } + else { + $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $matchedIDs[0]; + } + } + else { + return civicrm_api3_create_error('No match found for specified contact in contribution data. Row was skipped.'); + } + } + } + + if (!empty($params['pledge_id'])) { + if (CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $params['pledge_id'], 'contact_id') != $contributionContactID) { + return civicrm_api3_create_error('Invalid Pledge ID provided. Contribution row was skipped.'); + } + $values['pledge_id'] = $params['pledge_id']; + } + else { + // check if there are any pledge related to this contact, with payments pending or in progress + require_once 'CRM/Pledge/BAO/Pledge.php'; + $pledgeDetails = CRM_Pledge_BAO_Pledge::getContactPledges($contributionContactID); + + if (empty($pledgeDetails)) { + return civicrm_api3_create_error('No open pledges found for this contact. Contribution row was skipped.'); + } + elseif (count($pledgeDetails) > 1) { + return civicrm_api3_create_error('This contact has more than one open pledge. Unable to determine which pledge to apply the contribution to. Contribution row was skipped.'); + } + + // this mean we have only one pending / in progress pledge + $values['pledge_id'] = $pledgeDetails[0]; + } + + // we need to check if oldest payment amount equal to contribution amount + require_once 'CRM/Pledge/BAO/PledgePayment.php'; + $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($values['pledge_id']); + + if ($pledgePaymentDetails['amount'] == $totalAmount) { + $values['pledge_payment_id'] = $pledgePaymentDetails['id']; + } + else { + return civicrm_api3_create_error('Contribution and Pledge Payment amount mismatch for this record. Contribution row was skipped.'); + } + break; + + default: + // Hande name or label for fields with options. + if (isset($fields[$key]) && + // Yay - just for a surprise we are inconsistent on whether we pass the pseudofield (payment_instrument) + // or the field name (contribution_status_id) + (!empty($fields[$key]['is_pseudofield_for']) || !empty($fields[$key]['pseudoconstant'])) + ) { + $realField = $fields[$key]['is_pseudofield_for'] ?? $key; + $realFieldSpec = $fields[$realField]; + $values[$key] = $this->parsePseudoConstantField($value, $realFieldSpec); + } + break; + } + } + + if (array_key_exists('note', $params)) { + $values['note'] = $params['note']; + } + + if ($create) { + // CRM_Contribute_BAO_Contribution::add() handles contribution_source + // So, if $values contains contribution_source, convert it to source + $changes = ['contribution_source' => 'source']; + + foreach ($changes as $orgVal => $changeVal) { + if (isset($values[$orgVal])) { + $values[$changeVal] = $values[$orgVal]; + unset($values[$orgVal]); + } + } + } + + return NULL; + } + } diff --git a/CRM/Contribute/Info.php b/CRM/Contribute/Info.php index 7fccbdda920b..6fd1c9c51c39 100644 --- a/CRM/Contribute/Info.php +++ b/CRM/Contribute/Info.php @@ -1,9 +1,9 @@ 'CiviContribute', 'translatedName' => ts('CiviContribute'), 'title' => ts('CiviCRM Contribution Engine'), 'search' => 1, 'showActivitiesInCore' => 1, - ); + ]; } /** @@ -80,23 +82,23 @@ public function getInfo() { * collection of permissions, null if none */ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = FALSE) { - $permissions = array( - 'access CiviContribute' => array( + $permissions = [ + 'access CiviContribute' => [ ts('access CiviContribute'), ts('Record backend contributions (with edit contributions) and view all contributions (for visible contacts)'), - ), - 'edit contributions' => array( + ], + 'edit contributions' => [ ts('edit contributions'), ts('Record and update contributions'), - ), - 'make online contributions' => array( + ], + 'make online contributions' => [ ts('make online contributions'), - ), - 'delete in CiviContribute' => array( + ], + 'delete in CiviContribute' => [ ts('delete in CiviContribute'), ts('Delete contributions'), - ), - ); + ], + ]; if (!$descriptions) { foreach ($permissions as $name => $attr) { @@ -114,13 +116,14 @@ public function getPermissions($getAllUnconditionally = FALSE, $descriptions = F * list of permissions * @see CRM_Component_Info::getPermissions */ + /** * @return array */ public function getAnonymousPermissionWarnings() { - return array( + return [ 'access CiviContribute', - ); + ]; } /** @@ -132,16 +135,17 @@ public function getAnonymousPermissionWarnings() { * collection of required dashboard settings, * null if no element offered */ + /** * @return array|null */ public function getUserDashboardElement() { - return array( + return [ 'name' => ts('Contributions'), 'title' => ts('Your Contribution(s)'), - 'perm' => array('make online contributions'), + 'perm' => ['make online contributions'], 'weight' => 10, - ); + ]; } /** @@ -153,15 +157,24 @@ public function getUserDashboardElement() { * collection of required dashboard settings, * null if no element offered */ + /** * @return array|null */ public function registerTab() { - return array( + return [ 'title' => ts('Contributions'), 'url' => 'contribution', 'weight' => 20, - ); + ]; + } + + /** + * @inheritDoc + * @return string + */ + public function getIcon() { + return 'crm-i fa-credit-card'; } /** @@ -173,14 +186,15 @@ public function registerTab() { * collection of required pane settings, * null if no element offered */ + /** * @return array|null */ public function registerAdvancedSearchPane() { - return array( + return [ 'title' => ts('Contributions'), 'weight' => 20, - ); + ]; } /** @@ -193,6 +207,7 @@ public function registerAdvancedSearchPane() { * @return array|null * collection of activity types */ + /** * @return array|null */ @@ -209,20 +224,20 @@ public function creatNewShortcut(&$shortCuts, $newCredit) { if (CRM_Core_Permission::check('access CiviContribute') && CRM_Core_Permission::check('edit contributions') ) { - $shortCut[] = array( + $shortCut[] = [ 'path' => 'civicrm/contribute/add', 'query' => "reset=1&action=add&context=standalone", 'ref' => 'new-contribution', 'title' => ts('Contribution'), - ); + ]; if ($newCredit) { $title = ts('Contribution') . '
      (' . ts('credit card') . ')'; - $shortCut[0]['shortCuts'][] = array( + $shortCut[0]['shortCuts'][] = [ 'path' => 'civicrm/contribute/add', 'query' => "reset=1&action=add&context=standalone&mode=live", 'ref' => 'new-contribution-cc', 'title' => $title, - ); + ]; } $shortCuts = array_merge($shortCuts, $shortCut); } diff --git a/CRM/Contribute/Page/AJAX.php b/CRM/Contribute/Page/AJAX.php index e51e17a243eb..9cbfacdd6af3 100644 --- a/CRM/Contribute/Page/AJAX.php +++ b/CRM/Contribute/Page/AJAX.php @@ -1,9 +1,9 @@ 'Integer', 'context' => 'String', - ); - $optionalParameters = array( + ]; + $optionalParameters = [ 'entityID' => 'Integer', 'isTest' => 'Integer', - ); + ]; $params = CRM_Core_Page_AJAX::defaultSortAndPagerParams(); $params += CRM_Core_Page_AJAX::validateParams($requiredParameters, $optionalParameters); diff --git a/CRM/Contribute/Page/ContributionPage.php b/CRM/Contribute/Page/ContributionPage.php index ba4786bef437..55b3b9a463b2 100644 --- a/CRM/Contribute/Page/ContributionPage.php +++ b/CRM/Contribute/Page/ContributionPage.php @@ -1,9 +1,9 @@ ts('Test-drive'), 'url' => $urlString, 'qs' => $urlParams . '&action=preview', + // Addresses https://lab.civicrm.org/dev/core/issues/658 + 'fe' => TRUE, 'uniqueName' => 'test_drive', ), ); @@ -245,21 +247,21 @@ public function &contributionLinks() { 'name' => ts('Current Month-To-Date'), 'title' => ts('Current Month-To-Date'), 'url' => $urlString, - 'qs' => "{$urlParams}&start={$monthDate}&end={$now}", + 'qs' => "{$urlParams}&receive_date_low={$monthDate}&receive_date_high={$now}", 'uniqueName' => 'current_month_to_date', ), CRM_Core_Action::REVERT => array( 'name' => ts('Fiscal Year-To-Date'), 'title' => ts('Fiscal Year-To-Date'), 'url' => $urlString, - 'qs' => "{$urlParams}&start={$yearDate}&end={$yearNow}", + 'qs' => "{$urlParams}&receive_date_low={$yearDate}&receive_date_high={$yearNow}", 'uniqueName' => 'fiscal_year_to_date', ), CRM_Core_Action::BROWSE => array( 'name' => ts('Cumulative'), 'title' => ts('Cumulative'), 'url' => $urlString, - 'qs' => "{$urlParams}&start=&end=$now", + 'qs' => "{$urlParams}&receive_date_low=&receive_date_high=$now", 'uniqueName' => 'cumulative', ), ); @@ -382,9 +384,18 @@ public function copy() { $this, TRUE, 0, 'GET' ); - CRM_Contribute_BAO_ContributionPage::copy($gid); + $copy = CRM_Contribute_BAO_ContributionPage::copy($gid); + + $urlString = CRM_Utils_System::currentPath(); + $urlParams = 'reset=1'; + + // Redirect to copied contribution page + if ($copy->id) { + $urlString = 'civicrm/admin/contribute/settings'; + $urlParams .= '&action=update&id=' . $copy->id; + } - CRM_Utils_System::redirect(CRM_Utils_System::url(CRM_Utils_System::currentPath(), 'reset=1')); + CRM_Utils_System::redirect(CRM_Utils_System::url($urlString, $urlParams)); } /** @@ -419,8 +430,10 @@ public function browse($action = NULL) { $params = array(); $whereClause = $this->whereClause($params, FALSE); - $this->pagerAToZ($whereClause, $params); - + $config = CRM_Core_Config::singleton(); + if ($config->includeAlphabeticalPager) { + $this->pagerAToZ($whereClause, $params); + } $params = array(); $whereClause = $this->whereClause($params, TRUE); $this->pager($whereClause, $params); diff --git a/CRM/Contribute/Page/ContributionRecur.php b/CRM/Contribute/Page/ContributionRecur.php index 977ec388a29a..7aa68b264e3d 100644 --- a/CRM/Contribute/Page/ContributionRecur.php +++ b/CRM/Contribute/Page/ContributionRecur.php @@ -1,9 +1,9 @@ id = $this->_id; - if ($recur->find(TRUE)) { - $values = array(); - CRM_Core_DAO::storeValues($recur, $values); - // if there is a payment processor ID, get the name of the payment processor - if (!empty($values['payment_processor_id'])) { - $values['payment_processor'] = CRM_Core_DAO::getFieldValue( - 'CRM_Financial_DAO_PaymentProcessor', - $values['payment_processor_id'], - 'name' - ); - } - $idFields = array('contribution_status_id', 'campaign_id'); - if (CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($values['id'])) { - $idFields[] = 'financial_type_id'; - } - foreach ($idFields as $idField) { - if (!empty($values[$idField])) { - $values[substr($idField, 0, -3)] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionRecur', $idField, $values[$idField]); - } + if (empty($this->_id)) { + CRM_Core_Error::statusBounce('Recurring contribution not found'); + } + + try { + $contributionRecur = civicrm_api3('ContributionRecur', 'getsingle', [ + 'id' => $this->_id, + ]); + } + catch (Exception $e) { + CRM_Core_Error::statusBounce('Recurring contribution not found (ID: ' . $this->_id); + } + + $contributionRecur['payment_processor'] = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessorName( + CRM_Utils_Array::value('payment_processor_id', $contributionRecur) + ); + $idFields = ['contribution_status_id', 'campaign_id', 'financial_type_id']; + foreach ($idFields as $idField) { + if (!empty($contributionRecur[$idField])) { + $contributionRecur[substr($idField, 0, -3)] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionRecur', $idField, $contributionRecur[$idField]); } + } - $this->assign('recur', $values); + // Add linked membership + $membership = civicrm_api3('Membership', 'get', [ + 'contribution_recur_id' => $contributionRecur['id'], + ]); + if (!empty($membership['count'])) { + $membershipDetails = reset($membership['values']); + $contributionRecur['membership_id'] = $membershipDetails['id']; + $contributionRecur['membership_name'] = $membershipDetails['membership_name']; } + + $groupTree = CRM_Core_BAO_CustomGroup::getTree('ContributionRecur', NULL, $contributionRecur['id']); + CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $groupTree, FALSE, NULL, NULL, NULL, $contributionRecur['id']); + + $this->assign('recur', $contributionRecur); + + $displayName = CRM_Contact_BAO_Contact::displayName($contributionRecur['contact_id']); + $this->assign('displayName', $displayName); + + // Check if this is default domain contact CRM-10482 + if (CRM_Contact_BAO_Contact::checkDomainContact($contributionRecur['contact_id'])) { + $displayName .= ' (' . ts('default organization') . ')'; + } + + // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container + CRM_Utils_System::setTitle(ts('View Recurring Contribution from') . ' ' . $displayName); } public function preProcess() { - $context = CRM_Utils_Request::retrieve('context', 'String', $this); $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'view'); $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); diff --git a/CRM/Contribute/Page/ContributionRecurPayments.php b/CRM/Contribute/Page/ContributionRecurPayments.php new file mode 100644 index 000000000000..91419ff36428 --- /dev/null +++ b/CRM/Contribute/Page/ContributionRecurPayments.php @@ -0,0 +1,225 @@ +id = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); + $this->contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE); + + $this->loadRelatedContributions(); + + return parent::run(); + } + + /** + * Loads contributions associated to the current recurring contribution being + * viewed. + */ + private function loadRelatedContributions() { + $relatedContributions = []; + + $relatedContributionsResult = civicrm_api3('Contribution', 'get', [ + 'sequential' => 1, + 'contribution_recur_id' => $this->id, + 'contact_id' => $this->contactId, + 'options' => ['limit' => 0], + 'contribution_test' => '', + ]); + + foreach ($relatedContributionsResult['values'] as $contribution) { + $this->insertAmountExpandingPaymentsControl($contribution); + $this->fixDateFormats($contribution); + $this->insertStatusLabels($contribution); + $this->insertContributionActions($contribution); + + if ($contribution['is_test']) { + $contribution['financial_type'] = CRM_Core_TestEntity::appendTestText($contribution['financial_type']); + } + $relatedContributions[] = $contribution; + } + + if (count($relatedContributions) > 0) { + $this->assign('contributionsCount', count($relatedContributions)); + $this->assign('relatedContributions', json_encode($relatedContributions)); + } + } + + /** + * Inserts a string into the array with the html used to show the expanding + * payments control, which loads when user clicks on the amount. + * + * @param array $contribution + * Reference to the array holding the contribution's data and where the + * control will be inserted into + */ + private function insertAmountExpandingPaymentsControl(&$contribution) { + $amount = CRM_Utils_Money::format($contribution['total_amount'], $contribution['currency']); + + $expandPaymentsUrl = CRM_Utils_System::url('civicrm/payment', + [ + 'view' => 'transaction', + 'component' => 'contribution', + 'action' => 'browse', + 'cid' => $this->contactId, + 'id' => $contribution['contribution_id'], + 'selector' => 1, + ], + FALSE, NULL, TRUE + ); + + $contribution['amount_control'] = ' + +   ' . $amount . ' + + '; + } + + /** + * Fixes date fields present in the given contribution. + * + * @param array $contribution + * Reference to the array holding the contribution's data + */ + private function fixDateFormats(&$contribution) { + $config = CRM_Core_Config::singleton(); + + $contribution['formatted_receive_date'] = CRM_Utils_Date::customFormat($contribution['receive_date'], $config->dateformatDatetime); + $contribution['formatted_thankyou_date'] = CRM_Utils_Date::customFormat($contribution['thankyou_date'], $config->dateformatDatetime); + } + + /** + * Inserts a contribution_status_label key into the array, with the value + * showing the current status plus observations on the current status. + * + * @param array $contribution + * Reference to the array holding the contribution's data and where the new + * position will be inserted + */ + private function insertStatusLabels(&$contribution) { + $contribution['contribution_status_label'] = $contribution['contribution_status']; + + if ($contribution['is_pay_later'] && CRM_Utils_Array::value('contribution_status', $contribution) == 'Pending') { + $contribution['contribution_status_label'] .= ' (' . ts('Pay Later') . ')'; + } + elseif (CRM_Utils_Array::value('contribution_status', $contribution) == 'Pending') { + $contribution['contribution_status_label'] .= ' (' . ts('Incomplete Transaction') . ')'; + } + } + + /** + * Inserts into the given array a string with the 'action' key, holding the + * html to be used to show available actions for the contribution. + * + * @param $contribution + * Reference to the array holding the contribution's data. It is also the + * array where the new 'action' key will be inserted. + */ + private function insertContributionActions(&$contribution) { + $contribution['action'] = CRM_Core_Action::formLink( + $this->buildContributionLinks($contribution), + $this->getContributionPermissionsMask(), + [ + 'id' => $contribution['contribution_id'], + 'cid' => $contribution['contact_id'], + 'cxt' => 'contribution', + ], + ts('more'), + FALSE, + 'contribution.selector.row', + 'Contribution', + $contribution['contribution_id'] + ); + } + + /** + * Builds list of links for authorized actions that can be done on given + * contribution. + * + * @param array $contribution + * + * @return array + */ + private function buildContributionLinks($contribution) { + $links = CRM_Contribute_Selector_Search::links($contribution['contribution_id'], + CRM_Utils_Request::retrieve('action', 'String'), + NULL, + NULL + ); + + $isPayLater = FALSE; + if ($contribution['is_pay_later'] && CRM_Utils_Array::value('contribution_status', $contribution) == 'Pending') { + $isPayLater = TRUE; + + $links[CRM_Core_Action::ADD] = [ + 'name' => ts('Pay with Credit Card'), + 'url' => 'civicrm/contact/view/contribution', + 'qs' => 'reset=1&action=update&id=%%id%%&cid=%%cid%%&context=%%cxt%%&mode=live', + 'title' => ts('Pay with Credit Card'), + ]; + } + + if (in_array($contribution['contribution_status'], ['Partially paid', 'Pending refund']) || $isPayLater) { + $buttonName = ts('Record Payment'); + + if ($contribution['contribution_status'] == 'Pending refund') { + $buttonName = ts('Record Refund'); + } + elseif (CRM_Core_Config::isEnabledBackOfficeCreditCardPayments()) { + $links[CRM_Core_Action::BASIC] = [ + 'name' => ts('Submit Credit Card payment'), + 'url' => 'civicrm/payment/add', + 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=add&component=contribution&mode=live', + 'title' => ts('Submit Credit Card payment'), + ]; + } + $links[CRM_Core_Action::ADD] = [ + 'name' => $buttonName, + 'url' => 'civicrm/payment', + 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=add&component=contribution', + 'title' => $buttonName, + ]; + } + + return $links; + } + + /** + * Builds a mask with allowed contribution related permissions. + * + * @return int + */ + private function getContributionPermissionsMask() { + $permissions = [CRM_Core_Permission::VIEW]; + if (CRM_Core_Permission::check('edit contributions')) { + $permissions[] = CRM_Core_Permission::EDIT; + } + if (CRM_Core_Permission::check('delete in CiviContribute')) { + $permissions[] = CRM_Core_Permission::DELETE; + } + + return CRM_Core_Action::mask($permissions); + } + +} diff --git a/CRM/Contribute/Page/DashBoard.php b/CRM/Contribute/Page/DashBoard.php index 0f46943431c2..59b07ffda409 100644 --- a/CRM/Contribute/Page/DashBoard.php +++ b/CRM/Contribute/Page/DashBoard.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/admin/contribute/managePremiums', 'qs' => 'action=update&id=%%id%%&reset=1', 'title' => ts('Edit Premium'), - ), - CRM_Core_Action::PREVIEW => array( + ], + CRM_Core_Action::PREVIEW => [ 'name' => ts('Preview'), 'url' => 'civicrm/admin/contribute/managePremiums', 'qs' => 'action=preview&id=%%id%%', 'title' => ts('Preview Premium'), - ), - CRM_Core_Action::DISABLE => array( + ], + CRM_Core_Action::DISABLE => [ 'name' => ts('Disable'), 'ref' => 'crm-enable-disable', 'title' => ts('Disable Premium'), - ), - CRM_Core_Action::ENABLE => array( + ], + CRM_Core_Action::ENABLE => [ 'name' => ts('Enable'), 'ref' => 'crm-enable-disable', 'title' => ts('Enable Premium'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/admin/contribute/managePremiums', 'qs' => 'action=delete&id=%%id%%', 'title' => ts('Delete Premium'), - ), - ); + ], + ]; } return self::$_links; } @@ -105,28 +105,17 @@ public function &links() { * Finally it calls the parent's run method. */ public function run() { - - // get the requested action - $action = CRM_Utils_Request::retrieve('action', 'String', - // default to 'browse' - $this, FALSE, 'browse' - ); - - // assign vars to templates - $this->assign('action', $action); - $id = CRM_Utils_Request::retrieve('id', 'Positive', - $this, FALSE, 0 - ); + $id = $this->getIdAndAction(); // what action to take ? - if ($action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD | CRM_Core_Action::PREVIEW)) { - $this->edit($action, $id, TRUE); + if (!($this->_action & CRM_Core_Action::BROWSE)) { + $this->edit($this->_action, $id, TRUE); } // finally browse the custom groups $this->browse(); // parent run - return parent::run(); + return CRM_Core_Page::run(); } /** @@ -134,13 +123,13 @@ public function run() { */ public function browse() { // get all custom groups sorted by weight - $premiums = array(); + $premiums = []; $dao = new CRM_Contribute_DAO_Product(); $dao->orderBy('name'); $dao->find(); while ($dao->fetch()) { - $premiums[$dao->id] = array(); + $premiums[$dao->id] = []; CRM_Core_DAO::storeValues($dao, $premiums[$dao->id]); // form all action links $action = array_sum(array_keys($this->links())); @@ -154,17 +143,16 @@ public function browse() { $premiums[$dao->id]['action'] = CRM_Core_Action::formLink(self::links(), $action, - array('id' => $dao->id), + ['id' => $dao->id], ts('more'), FALSE, 'premium.manage.row', 'Premium', $dao->id ); - //Financial Type + // Financial Type if (!empty($dao->financial_type_id)) { - require_once 'CRM/Core/DAO.php'; - $premiums[$dao->id]['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $dao->financial_type_id, 'name'); + $premiums[$dao->id]['financial_type'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Product', 'financial_type_id', $dao->financial_type_id); } } $this->assign('rows', $premiums); diff --git a/CRM/Contribute/Page/PaymentInfo.php b/CRM/Contribute/Page/PaymentInfo.php index 8227ff067f73..bfb9e8436180 100644 --- a/CRM/Contribute/Page/PaymentInfo.php +++ b/CRM/Contribute/Page/PaymentInfo.php @@ -1,9 +1,9 @@ _component = CRM_Utils_Request::retrieve('component', 'String', $this, TRUE); $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE); - $this->_context = CRM_Utils_Request::retrieve('context', 'String', $this, TRUE); + $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, TRUE); $this->_cid = CRM_Utils_Request::retrieve('cid', 'String', $this, TRUE); $this->assign('cid', $this->_cid); diff --git a/CRM/Contribute/Page/Premium.php b/CRM/Contribute/Page/Premium.php index e0686b459e64..25189668ad14 100644 --- a/CRM/Contribute/Page/Premium.php +++ b/CRM/Contribute/Page/Premium.php @@ -1,9 +1,9 @@ array( + self::$_links = [ + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/admin/contribute/addProductToPage', 'qs' => 'action=update&id=%%id%%&pid=%%pid%%&reset=1', 'title' => ts('Edit Premium'), - ), - CRM_Core_Action::PREVIEW => array( + ], + CRM_Core_Action::PREVIEW => [ 'name' => ts('Preview'), 'url' => 'civicrm/admin/contribute/addProductToPage', 'qs' => 'action=preview&id=%%id%%&pid=%%pid%%', 'title' => ts('Preview Premium'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Remove'), 'url' => 'civicrm/admin/contribute/addProductToPage', 'qs' => 'action=delete&id=%%id%%&pid=%%pid%%', 'extra' => 'onclick = "if (confirm(\'' . $deleteExtra . '\') ) this.href+=\'&confirmed=1\'; else return false;"', 'title' => ts('Disable Premium'), - ), - ); + ], + ]; } return self::$_links; } @@ -126,49 +126,49 @@ public function run() { */ public function browse() { // get all custom groups sorted by weight - $premiums = array(); + $premiums = []; $pageID = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE, 0 ); - $dao = new CRM_Contribute_DAO_Premium(); - $dao->entity_table = 'civicrm_contribution_page'; - $dao->entity_id = $pageID; - $dao->find(TRUE); - $premiumID = $dao->id; + $premiumDao = new CRM_Contribute_DAO_Premium(); + $premiumDao->entity_table = 'civicrm_contribution_page'; + $premiumDao->entity_id = $pageID; + $premiumDao->find(TRUE); + $premiumID = $premiumDao->id; $this->assign('products', FALSE); $this->assign('id', $pageID); if (!$premiumID) { return; } - $dao = new CRM_Contribute_DAO_PremiumsProduct(); - $dao->premiums_id = $premiumID; - $dao->orderBy('weight'); - $dao->find(); + $premiumsProductDao = new CRM_Contribute_DAO_PremiumsProduct(); + $premiumsProductDao->premiums_id = $premiumID; + $premiumsProductDao->orderBy('weight'); + $premiumsProductDao->find(); - while ($dao->fetch()) { + while ($premiumsProductDao->fetch()) { $productDAO = new CRM_Contribute_DAO_Product(); - $productDAO->id = $dao->product_id; + $productDAO->id = $premiumsProductDao->product_id; $productDAO->is_active = 1; if ($productDAO->find(TRUE)) { - $premiums[$productDAO->id] = array(); - $premiums[$productDAO->id]['weight'] = $dao->weight; + $premiums[$productDAO->id] = []; + $premiums[$productDAO->id]['weight'] = $premiumsProductDao->weight; CRM_Core_DAO::storeValues($productDAO, $premiums[$productDAO->id]); $action = array_sum(array_keys($this->links())); - $premiums[$dao->product_id]['action'] = CRM_Core_Action::formLink(self::links(), $action, - array('id' => $pageID, 'pid' => $dao->id), + $premiums[$premiumsProductDao->product_id]['action'] = CRM_Core_Action::formLink(self::links(), $action, + ['id' => $pageID, 'pid' => $premiumsProductDao->id], ts('more'), FALSE, 'premium.contributionpage.row', 'Premium', - $dao->id + $premiumsProductDao->id ); - //Financial Type - if (!empty($dao->financial_type_id)) { - $premiums[$productDAO->id]['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', $dao->financial_type_id, 'name'); + // Financial Type + if (!empty($premiumsProductDao->financial_type_id)) { + $premiums[$productDAO->id]['financial_type'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_Product', 'financial_type_id', $premiumsProductDao->financial_type_id); } } } diff --git a/CRM/Contribute/Page/SubscriptionStatus.php b/CRM/Contribute/Page/SubscriptionStatus.php index 4e2aa09863af..96e7f0786f92 100644 --- a/CRM/Contribute/Page/SubscriptionStatus.php +++ b/CRM/Contribute/Page/SubscriptionStatus.php @@ -1,9 +1,9 @@ array( - 'name' => ts('View'), - 'title' => ts('View Recurring Payment'), - 'url' => 'civicrm/contact/view/contributionrecur', - 'qs' => "reset=1&id=%%crid%%&cid=%%cid%%&context={$context}", - ), - CRM_Core_Action::UPDATE => array( - 'name' => ts('Edit'), - 'title' => ts('Edit Recurring Payment'), - 'url' => 'civicrm/contribute/updaterecur', - 'qs' => "reset=1&action=update&crid=%%crid%%&cid=%%cid%%&context={$context}", - ), - CRM_Core_Action::DISABLE => array( - 'name' => ts('Cancel'), - 'title' => ts('Cancel'), - 'ref' => 'crm-enable-disable', - ), - ); - } + public static function recurLinks($recurID = FALSE, $context = 'contribution') { + $links = [ + CRM_Core_Action::VIEW => [ + 'name' => ts('View'), + 'title' => ts('View Recurring Payment'), + 'url' => 'civicrm/contact/view/contributionrecur', + 'qs' => "reset=1&id=%%crid%%&cid=%%cid%%&context={$context}", + ], + CRM_Core_Action::UPDATE => [ + 'name' => ts('Edit'), + 'title' => ts('Edit Recurring Payment'), + 'url' => 'civicrm/contribute/updaterecur', + 'qs' => "reset=1&action=update&crid=%%crid%%&cid=%%cid%%&context={$context}", + ], + CRM_Core_Action::DISABLE => [ + 'name' => ts('Cancel'), + 'title' => ts('Cancel'), + 'ref' => 'crm-enable-disable', + ], + ]; if ($recurID) { - $links = self::$_links; - $paymentProcessorObj = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($recurID, 'recur', 'obj'); - if (is_object($paymentProcessorObj) && $paymentProcessorObj->supports('cancelRecurring')) { - unset($links[CRM_Core_Action::DISABLE]['extra'], $links[CRM_Core_Action::DISABLE]['ref']); - $links[CRM_Core_Action::DISABLE]['url'] = "civicrm/contribute/unsubscribe"; - $links[CRM_Core_Action::DISABLE]['qs'] = "reset=1&crid=%%crid%%&cid=%%cid%%&context={$context}"; - } + $paymentProcessorObj = CRM_Contribute_BAO_ContributionRecur::getPaymentProcessorObject($recurID); + if ($paymentProcessorObj) { + if ($paymentProcessorObj->supports('cancelRecurring')) { + unset($links[CRM_Core_Action::DISABLE]['extra'], $links[CRM_Core_Action::DISABLE]['ref']); + $links[CRM_Core_Action::DISABLE]['url'] = "civicrm/contribute/unsubscribe"; + $links[CRM_Core_Action::DISABLE]['qs'] = "reset=1&crid=%%crid%%&cid=%%cid%%&context={$context}"; + } - if (is_object($paymentProcessorObj) && $paymentProcessorObj->isSupported('updateSubscriptionBillingInfo')) { - $links[CRM_Core_Action::RENEW] = array( - 'name' => ts('Change Billing Details'), - 'title' => ts('Change Billing Details'), - 'url' => 'civicrm/contribute/updatebilling', - 'qs' => "reset=1&crid=%%crid%%&cid=%%cid%%&context={$context}", - ); + if ($paymentProcessorObj->supports('UpdateSubscriptionBillingInfo')) { + $links[CRM_Core_Action::RENEW] = [ + 'name' => ts('Change Billing Details'), + 'title' => ts('Change Billing Details'), + 'url' => 'civicrm/contribute/updatebilling', + 'qs' => "reset=1&crid=%%crid%%&cid=%%cid%%&context={$context}", + ]; + } + + if (!$paymentProcessorObj->supports('ChangeSubscriptionAmount') && !$paymentProcessorObj->supports('EditRecurringContribution')) { + unset($links[CRM_Core_Action::UPDATE]); + } + } + else { + unset($links[CRM_Core_Action::DISABLE]); + unset($links[CRM_Core_Action::UPDATE]); } - return $links; } - return self::$_links; + return $links; } - // end function /** * called when action is browse. @@ -108,7 +121,7 @@ public static function &recurLinks($recurID = FALSE, $context = 'contribution') */ public function browse() { // add annual contribution - $annual = array(); + $annual = []; list($annual['count'], $annual['amount'], $annual['avg'] @@ -131,44 +144,9 @@ public function browse() { $controller->run(); // add recurring block - $action = array_sum(array_keys($this->recurLinks())); - $params = CRM_Contribute_BAO_ContributionRecur::getRecurContributions($this->_contactId); - - if (!empty($params)) { - foreach ($params as $ids => $recur) { - $action = array_sum(array_keys($this->recurLinks($ids))); - // no action allowed if it's not active - $params[$ids]['is_active'] = ($recur['contribution_status_id'] != 3); - - if ($params[$ids]['is_active']) { - $details = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($params[$ids]['id'], 'recur'); - $hideUpdate = $details->membership_id & $details->auto_renew; - - if ($hideUpdate) { - $action -= CRM_Core_Action::UPDATE; - } + $this->addRecurringContributionsBlock(); - $params[$ids]['action'] = CRM_Core_Action::formLink(self::recurLinks($ids), $action, - array( - 'cid' => $this->_contactId, - 'crid' => $ids, - 'cxt' => 'contribution', - ), - ts('more'), - FALSE, - 'contribution.selector.recurring', - 'Contribution', - $ids - ); - } - } - // assign vars to templates - $this->assign('action', $this->_action); - $this->assign('recurRows', $params); - $this->assign('recur', TRUE); - } - - //enable/disable soft credit records for test contribution + // enable/disable soft credit records for test contribution $isTest = 0; if (CRM_Utils_Request::retrieve('isTest', 'Positive', $this)) { $isTest = 1; @@ -178,12 +156,14 @@ public function browse() { $softCreditList = CRM_Contribute_BAO_ContributionSoft::getSoftContributionList($this->_contactId, NULL, $isTest); if (!empty($softCreditList)) { - $softCreditTotals = array(); + $softCreditTotals = []; - list($softCreditTotals['amount'], + list($softCreditTotals['count'], + $softCreditTotals['cancel']['count'], + $softCreditTotals['amount'], $softCreditTotals['avg'], - $softCreditTotals['currency'], - $softCreditTotals['cancelAmount'] //to get cancel amount + // to get cancel amount + $softCreditTotals['cancel']['amount'] ) = CRM_Contribute_BAO_ContributionSoft::getSoftContributionTotals($this->_contactId, $isTest); $this->assign('softCredit', TRUE); @@ -194,14 +174,124 @@ public function browse() { if ($this->_contactId) { $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId); $this->assign('displayName', $displayName); - $this->ajaxResponse['tabCount'] = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); + $tabCount = CRM_Contact_BAO_Contact::getCountComponent('contribution', $this->_contactId); + $this->assign('tabCount', $tabCount); + $this->ajaxResponse['tabCount'] = $tabCount; + } + } + + /** + * Get all the recurring contribution information and assign to the template + */ + private function addRecurringContributionsBlock() { + list($activeContributions, $activeContributionsCount) = $this->getActiveRecurringContributions(); + list($inactiveRecurringContributions, $inactiveContributionsCount) = $this->getInactiveRecurringContributions(); + + if (!empty($activeContributions) || !empty($inactiveRecurringContributions)) { + // assign vars to templates + $this->assign('action', $this->_action); + $this->assign('activeRecurRows', $activeContributions); + $this->assign('contributionRecurCount', $activeContributionsCount + $inactiveContributionsCount); + $this->assign('inactiveRecurRows', $inactiveRecurringContributions); + $this->assign('recur', TRUE); + } + } + + /** + * Loads active recurring contributions for the current contact and formats + * them to be used on the form. + * + * @return array + */ + private function getActiveRecurringContributions() { + try { + $contributionRecurResult = civicrm_api3('ContributionRecur', 'get', [ + 'contact_id' => $this->_contactId, + 'contribution_status_id' => ['NOT IN' => CRM_Contribute_BAO_ContributionRecur::getInactiveStatuses()], + 'options' => ['limit' => 0, 'sort' => 'is_test, start_date DESC'], + ]); + $recurContributions = CRM_Utils_Array::value('values', $contributionRecurResult); + } + catch (Exception $e) { + $recurContributions = []; + } + + return $this->buildRecurringContributionsArray($recurContributions); + } + + /** + * Loads inactive recurring contributions for the current contact and formats + * them to be used on the form. + * + * @return array + */ + private function getInactiveRecurringContributions() { + try { + $contributionRecurResult = civicrm_api3('ContributionRecur', 'get', [ + 'contact_id' => $this->_contactId, + 'contribution_status_id' => ['IN' => CRM_Contribute_BAO_ContributionRecur::getInactiveStatuses()], + 'options' => ['limit' => 0, 'sort' => 'is_test, start_date DESC'], + ]); + $recurContributions = CRM_Utils_Array::value('values', $contributionRecurResult); + } + catch (Exception $e) { + $recurContributions = NULL; + } + + return $this->buildRecurringContributionsArray($recurContributions); + } + + /** + * @param $recurContributions + * + * @return array + */ + private function buildRecurringContributionsArray($recurContributions) { + $liveRecurringContributionCount = 0; + foreach ($recurContributions as $recurId => $recurDetail) { + // Is recurring contribution active? + $recurContributions[$recurId]['is_active'] = !in_array(CRM_Contribute_PseudoConstant::contributionStatus($recurDetail['contribution_status_id'], 'name'), CRM_Contribute_BAO_ContributionRecur::getInactiveStatuses()); + if ($recurContributions[$recurId]['is_active']) { + $actionMask = array_sum(array_keys(self::recurLinks($recurId))); + } + else { + $actionMask = CRM_Core_Action::mask([CRM_Core_Permission::VIEW]); + } + + if (empty($recurDetail['is_test'])) { + $liveRecurringContributionCount++; + } + + // Get the name of the payment processor + if (!empty($recurDetail['payment_processor_id'])) { + $recurContributions[$recurId]['payment_processor'] = CRM_Financial_BAO_PaymentProcessor::getPaymentProcessorName($recurDetail['payment_processor_id']); + } + // Get the label for the contribution status + if (!empty($recurDetail['contribution_status_id'])) { + $recurContributions[$recurId]['contribution_status'] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $recurDetail['contribution_status_id']); + } + + $recurContributions[$recurId]['action'] = CRM_Core_Action::formLink(self::recurLinks($recurId), $actionMask, + [ + 'cid' => $this->_contactId, + 'crid' => $recurId, + 'cxt' => 'contribution', + ], + ts('more'), + FALSE, + 'contribution.selector.recurring', + 'Contribution', + $recurId + ); } + + return [$recurContributions, $liveRecurringContributionCount]; } /** * called when action is view. * - * @return null + * @return mixed */ public function view() { $controller = new CRM_Core_Controller_Simple( @@ -219,11 +309,13 @@ public function view() { /** * called when action is update or new. * - * @return null + * @return mixed + * @throws \CRM_Core_Exception + * @throws \Exception */ public function edit() { // set https for offline cc transaction - $mode = CRM_Utils_Request::retrieve('mode', 'String', $this); + $mode = CRM_Utils_Request::retrieve('mode', 'Alphanumeric', $this); if ($mode == 'test' || $mode == 'live') { CRM_Utils_System::redirectToSSL(); } @@ -240,8 +332,12 @@ public function edit() { return $controller->run(); } + /** + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ public function preProcess() { - $context = CRM_Utils_Request::retrieve('context', 'String', $this); + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this); $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse'); $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this); @@ -251,10 +347,10 @@ public function preProcess() { else { $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, empty($this->_id)); if (empty($this->_contactId)) { - $this->_contactId = civicrm_api3('contribution', 'getvalue', array( - 'id' => $this->_id, - 'return' => 'contact_id', - )); + $this->_contactId = civicrm_api3('contribution', 'getvalue', [ + 'id' => $this->_id, + 'return' => 'contact_id', + ]); } $this->assign('contactId', $this->_contactId); @@ -274,7 +370,8 @@ public function preProcess() { * the main function that is called when the page * loads, it decides the which action has to be taken for the page. * - * @return null + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public function run() { $this->preProcess(); @@ -294,12 +391,15 @@ public function run() { $this->browse(); } - return parent::run(); + parent::run(); } + /** + * @throws \CRM_Core_Exception + */ public function setContext() { $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this); - $context = CRM_Utils_Request::retrieve('context', 'String', + $context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this, FALSE, 'search' ); $compContext = CRM_Utils_Request::retrieve('compContext', 'String', $this); @@ -320,8 +420,6 @@ public function setContext() { $qfKey = NULL; } - $session = CRM_Core_Session::singleton(); - switch ($context) { case 'user': $url = CRM_Utils_System::url('civicrm/user', 'reset=1'); diff --git a/CRM/Contribute/Page/UserDashboard.php b/CRM/Contribute/Page/UserDashboard.php index a3dfb350423d..c0a635ad4dfb 100644 --- a/CRM/Contribute/Page/UserDashboard.php +++ b/CRM/Contribute/Page/UserDashboard.php @@ -1,9 +1,9 @@ setEmbedded(TRUE); - $controller->reset(); - $controller->set('limit', 12); - $controller->set('cid', $this->_contactId); - $controller->set('context', 'user'); - $controller->set('force', 1); - $controller->process(); - $controller->run(); + $rows = civicrm_api3('Contribution', 'get', [ + 'options' => [ + 'limit' => 12, + 'sort' => 'receive_date DESC', + ], + 'sequential' => 1, + 'contact_id' => $this->_contactId, + 'return' => [ + 'total_amount', + 'contribution_recur_id', + 'financial_type', + 'receive_date', + 'receipt_date', + 'contribution_status', + 'currency', + 'amount_level', + 'contact_id,', + 'contribution_source', + ], + ])['values']; + + // We want oldest first, just among the most recent contributions + $rows = array_reverse($rows); + + foreach ($rows as $index => &$row) { + // This is required for tpl logic. We should move away from hard-code this to adding an array of actions to the row + // which the tpl can iterate through - this should allow us to cope with competing attempts to add new buttons + // and allow extensions to assign new ones through the pageRun hook + if ('Pending' === CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $row['contribution_status_id'])) { + $row['buttons']['pay'] = [ + 'class' => 'button', + 'label' => ts('Pay Now'), + 'url' => CRM_Utils_System::url('civicrm/contribute/transact', [ + 'reset' => 1, + 'id' => CRM_Invoicing_Utils::getDefaultPaymentPage(), + 'ccid' => $row['contribution_id'], + 'cs' => $this->getUserChecksum(), + 'cid' => $row['contact_id'], + ]), + ]; + } + } + + $this->assign('contribute_rows', $rows); + $this->assign('contributionSummary', ['total_amount' => civicrm_api3('Contribution', 'getcount', ['contact_id' => $this->_contactId])]); //add honor block $params = CRM_Contribute_BAO_Contribution::getHonorContacts($this->_contactId); @@ -65,18 +96,14 @@ public function listContribution() { $recur->is_test = 0; $recur->find(); - $config = CRM_Core_Config::singleton(); + $recurStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'label'); - $recurStatus = CRM_Contribute_PseudoConstant::contributionStatus(); - - $recurRow = array(); - $recurIDs = array(); + $recurRow = []; + $recurIDs = []; while ($recur->fetch()) { - $mode = $recur->is_test ? 'test' : 'live'; - $paymentProcessor = CRM_Contribute_BAO_ContributionRecur::getPaymentProcessor($recur->id, - $mode - ); - if (!$paymentProcessor) { + if (empty($recur->payment_processor_id)) { + // it's not clear why we continue here as any without a processor id would likely + // be imported from another system & still seem valid. continue; } @@ -97,11 +124,11 @@ public function listContribution() { } $recurRow[$values['id']]['action'] = CRM_Core_Action::formLink(CRM_Contribute_Page_Tab::recurLinks($recur->id, 'dashboard'), - $action, array( + $action, [ 'cid' => $this->_contactId, 'crid' => $values['id'], 'cxt' => 'contribution', - ), + ], ts('more'), FALSE, 'contribution.dashboard.recurring', @@ -110,10 +137,6 @@ public function listContribution() { ); $recurIDs[] = $values['id']; - - //reset $paymentObject for checking other paymenet processor - //recurring url - $paymentObject = NULL; } if (is_array($recurIDs) && !empty($recurIDs)) { $getCount = CRM_Contribute_BAO_ContributionRecur::getCount($recurIDs); @@ -134,16 +157,28 @@ public function listContribution() { } } + /** + * Should invoice links be displayed on the template. + * + * @todo This should be moved to a hook-like structure on the invoicing class + * (currently CRM_Utils_Invoicing) with a view to possible removal from core. + */ + public function isIncludeInvoiceLinks() { + if (!CRM_Invoicing_Utils::isInvoicingEnabled()) { + return FALSE; + } + $dashboardOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'user_dashboard_options' + ); + return $dashboardOptions['Invoices / Credit Notes']; + } + /** * the main function that is called when the page * loads, it decides the which action has to be taken for the page. */ public function run() { - $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); - $defaultInvoicePage = CRM_Utils_Array::value('default_invoice_page', $invoiceSettings); - $this->assign('invoicing', $invoicing); - $this->assign('defaultInvoicePage', $defaultInvoicePage); + $this->assign('isIncludeInvoiceLinks', $this->isIncludeInvoiceLinks()); parent::preProcess(); $this->listContribution(); } diff --git a/CRM/Contribute/PseudoConstant.php b/CRM/Contribute/PseudoConstant.php index 1cc6cd9859b1..37433be2e309 100644 --- a/CRM/Contribute/PseudoConstant.php +++ b/CRM/Contribute/PseudoConstant.php @@ -1,9 +1,9 @@ is_active = 1; $dao->orderBy('id'); @@ -270,7 +276,7 @@ public static function products($pageID = NULL) { $dao->find(TRUE); $premiumID = $dao->id; - $productID = array(); + $productID = []; $dao = new CRM_Contribute_DAO_PremiumsProduct(); $dao->premiums_id = $premiumID; @@ -279,7 +285,7 @@ public static function products($pageID = NULL) { $productID[$dao->product_id] = $dao->product_id; } - $tempProduct = array(); + $tempProduct = []; foreach ($products as $key => $value) { if (!array_key_exists($key, $productID)) { $tempProduct[$key] = $value; @@ -359,10 +365,10 @@ public static function &pcPage($pageType = NULL, $id = NULL) { */ public static function &pcpStatus($column = 'label') { if (NULL === self::$pcpStatus) { - self::$pcpStatus = array(); + self::$pcpStatus = []; } if (!array_key_exists($column, self::$pcpStatus)) { - self::$pcpStatus[$column] = array(); + self::$pcpStatus[$column] = []; self::$pcpStatus[$column] = CRM_Core_OptionGroup::values('pcp_status', FALSE, FALSE, FALSE, NULL, $column @@ -374,6 +380,8 @@ public static function &pcpStatus($column = 'label') { /** * Get financial account for a Financial type. * + * @deprecated use the alternative with caching + * CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship * * @param int $entityId * @param string $accountRelationType @@ -382,11 +390,11 @@ public static function &pcpStatus($column = 'label') { * @return int */ public static function getRelationalFinancialAccount($entityId, $accountRelationType, $entityTable = 'civicrm_financial_type', $returnField = 'financial_account_id') { - $params = array( - 'return' => array($returnField), + $params = [ + 'return' => [$returnField], 'entity_table' => $entityTable, 'entity_id' => $entityId, - ); + ]; if ($accountRelationType) { $params['account_relationship.name'] = $accountRelationType; } diff --git a/CRM/Contribute/Selector/Search.php b/CRM/Contribute/Selector/Search.php index e547a3813c4a..1c2d23a3bc42 100644 --- a/CRM/Contribute/Selector/Search.php +++ b/CRM/Contribute/Selector/Search.php @@ -1,9 +1,9 @@ _action = $action; - $returnProperties = CRM_Contribute_BAO_Query::selectorReturnProperties(); + $returnProperties = CRM_Contribute_BAO_Query::selectorReturnProperties($this->_queryParams); $this->_includeSoftCredits = CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled($this->_queryParams); + $this->_queryParams[] = ['contribution_id', '!=', 0, 0, 0]; $this->_query = new CRM_Contact_BAO_Query( $this->_queryParams, $returnProperties, @@ -226,26 +227,26 @@ public static function &links($componentId = NULL, $componentAction = NULL, $key } if (!(self::$_links)) { - self::$_links = array( - CRM_Core_Action::VIEW => array( + self::$_links = [ + CRM_Core_Action::VIEW => [ 'name' => ts('View'), 'url' => 'civicrm/contact/view/contribution', 'qs' => "reset=1&id=%%id%%&cid=%%cid%%&action=view&context=%%cxt%%&selectedChild=contribute{$extraParams}", 'title' => ts('View Contribution'), - ), - CRM_Core_Action::UPDATE => array( + ], + CRM_Core_Action::UPDATE => [ 'name' => ts('Edit'), 'url' => 'civicrm/contact/view/contribution', 'qs' => "reset=1&action=update&id=%%id%%&cid=%%cid%%&context=%%cxt%%{$extraParams}", 'title' => ts('Edit Contribution'), - ), - CRM_Core_Action::DELETE => array( + ], + CRM_Core_Action::DELETE => [ 'name' => ts('Delete'), 'url' => 'civicrm/contact/view/contribution', 'qs' => "reset=1&action=delete&id=%%id%%&cid=%%cid%%&context=%%cxt%%{$extraParams}", 'title' => ts('Delete Contribution'), - ), - ); + ], + ]; } return self::$_links; } @@ -316,10 +317,10 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $this->_contributionClause ); // process the result of the query - $rows = array(); + $rows = []; //CRM-4418 check for view/edit/delete - $permissions = array(CRM_Core_Permission::VIEW); + $permissions = [CRM_Core_Permission::VIEW]; if (CRM_Core_Permission::check('edit contributions')) { $permissions[] = CRM_Core_Permission::EDIT; } @@ -369,7 +370,7 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $componentContext ); $checkLineItem = FALSE; - $row = array(); + $row = []; // Now check for lineItems if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()) { $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($result->id); @@ -417,12 +418,12 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { if ($result->is_pay_later && CRM_Utils_Array::value('contribution_status_name', $row) == 'Pending') { $isPayLater = TRUE; $row['contribution_status'] .= ' (' . ts('Pay Later') . ')'; - $links[CRM_Core_Action::ADD] = array( + $links[CRM_Core_Action::ADD] = [ 'name' => ts('Pay with Credit Card'), 'url' => 'civicrm/contact/view/contribution', 'qs' => 'reset=1&action=update&id=%%id%%&cid=%%cid%%&context=%%cxt%%&mode=live', 'title' => ts('Pay with Credit Card'), - ); + ]; } elseif (CRM_Utils_Array::value('contribution_status_name', $row) == 'Pending') { $row['contribution_status'] .= ' (' . ts('Incomplete Transaction') . ')'; @@ -434,31 +435,31 @@ public function &getRows($action, $offset, $rowCount, $sort, $output = NULL) { $row['checkbox'] = CRM_Core_Form::CB_PREFIX . $result->contribution_id; - $actions = array( + $actions = [ 'id' => $result->contribution_id, 'cid' => $result->contact_id, 'cxt' => $this->_context, - ); + ]; - if (in_array($row['contribution_status_name'], array('Partially paid', 'Pending refund')) || $isPayLater) { + if (in_array($row['contribution_status_name'], ['Partially paid', 'Pending refund']) || $isPayLater) { $buttonName = ts('Record Payment'); if ($row['contribution_status_name'] == 'Pending refund') { $buttonName = ts('Record Refund'); } elseif (CRM_Core_Config::isEnabledBackOfficeCreditCardPayments()) { - $links[CRM_Core_Action::BASIC] = array( + $links[CRM_Core_Action::BASIC] = [ 'name' => ts('Submit Credit Card payment'), 'url' => 'civicrm/payment/add', 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=add&component=contribution&mode=live', 'title' => ts('Submit Credit Card payment'), - ); + ]; } - $links[CRM_Core_Action::ADD] = array( + $links[CRM_Core_Action::ADD] = [ 'name' => $buttonName, 'url' => 'civicrm/payment', 'qs' => 'reset=1&id=%%id%%&cid=%%cid%%&action=add&component=contribution', 'title' => $buttonName, - ); + ]; } $row['action'] = CRM_Core_Action::formLink( @@ -504,105 +505,107 @@ public function getQILL() { * the column headers that need to be displayed */ public function &getColumnHeaders($action = NULL, $output = NULL) { - $pre = array(); - self::$_columnHeaders = array( - array( + $pre = []; + self::$_columnHeaders = [ + [ 'name' => $this->_includeSoftCredits ? ts('Contribution Amount') : ts('Amount'), 'sort' => 'total_amount', 'direction' => CRM_Utils_Sort::DONTCARE, 'field_name' => 'total_amount', - ), - ); + ], + ]; if ($this->_includeSoftCredits) { self::$_columnHeaders = array_merge( self::$_columnHeaders, - array( - array( + [ + [ 'name' => ts('Soft Credit Amount'), 'sort' => 'contribution_soft_credit_amount', 'field_name' => 'contribution_soft_credit_amount', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - ) + ], + ] ); } self::$_columnHeaders = array_merge( self::$_columnHeaders, - array( - array( + [ + [ 'name' => ts('Type'), 'sort' => 'financial_type', 'field_name' => 'financial_type', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Source'), 'sort' => 'contribution_source', 'field_name' => 'contribution_source', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Received'), 'sort' => 'receive_date', 'field_name' => 'receive_date', 'type' => 'date', 'direction' => CRM_Utils_Sort::DESCENDING, - ), - array( + ], + [ 'name' => ts('Thank-you Sent'), 'sort' => 'thankyou_date', - 'field_name' => 'receive_date', + 'field_name' => 'thankyou_date', 'type' => 'date', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Status'), 'sort' => 'contribution_status', 'field_name' => 'contribution_status', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( - 'name' => ts('Premium'), - 'sort' => 'product_name', - 'field_name' => 'product_name', - 'direction' => CRM_Utils_Sort::DONTCARE, - ), - ) + ], + ] ); + if (CRM_Contribute_BAO_Query::isSiteHasProducts()) { + self::$_columnHeaders[] = [ + 'name' => ts('Premium'), + 'sort' => 'product_name', + 'field_name' => 'product_name', + 'direction' => CRM_Utils_Sort::DONTCARE, + ]; + } if (!$this->_single) { - $pre = array( - array( + $pre = [ + [ 'name' => ts('Name'), 'sort' => 'sort_name', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - ); + ], + ]; } self::$_columnHeaders = array_merge($pre, self::$_columnHeaders); if ($this->_includeSoftCredits) { self::$_columnHeaders = array_merge( self::$_columnHeaders, - array( - array( + [ + [ 'name' => ts('Soft Credit For'), 'sort' => 'contribution_soft_credit_name', 'direction' => CRM_Utils_Sort::DONTCARE, - ), - array( + ], + [ 'name' => ts('Soft Credit Type'), 'sort' => 'contribution_soft_credit_type', 'direction' => CRM_Utils_Sort::ASCENDING, - ), - ) + ], + ] ); } self::$_columnHeaders = array_merge( - self::$_columnHeaders, array( - array('desc' => ts('Actions'), 'type' => 'actions'), - ) + self::$_columnHeaders, [ + ['desc' => ts('Actions'), 'type' => 'actions'], + ] ); foreach (array_keys(self::$_columnHeaders) as $index) { // Add weight & space it out a bit to allow headers to be inserted. @@ -617,7 +620,7 @@ public function &getColumnHeaders($action = NULL, $output = NULL) { * @return mixed */ public function alphabetQuery() { - return $this->_query->searchQuery(NULL, NULL, NULL, FALSE, FALSE, TRUE); + return $this->_query->alphabetQuery(); } /** diff --git a/CRM/Contribute/StateMachine/Contribution.php b/CRM/Contribute/StateMachine/Contribution.php index 08b5bda75d1a..f7c598805c53 100644 --- a/CRM/Contribute/StateMachine/Contribution.php +++ b/CRM/Contribute/StateMachine/Contribution.php @@ -1,9 +1,9 @@ _pages = array( + $this->_pages = [ 'CRM_Contribute_Form_Contribution_Main' => NULL, 'CRM_Contribute_Form_Contribution_Confirm' => NULL, 'CRM_Contribute_Form_Contribution_ThankYou' => NULL, - ); + ]; $this->addSequentialPages($this->_pages, $action); } diff --git a/CRM/Contribute/StateMachine/ContributionPage.php b/CRM/Contribute/StateMachine/ContributionPage.php index bc225cfc86bc..47ccc247b88b 100644 --- a/CRM/Contribute/StateMachine/ContributionPage.php +++ b/CRM/Contribute/StateMachine/ContributionPage.php @@ -1,9 +1,9 @@ _pages = array( + $this->_pages = [ 'CRM_Contribute_Form_ContributionPage_Settings' => NULL, 'CRM_Contribute_Form_ContributionPage_Amount' => NULL, 'CRM_Member_Form_MembershipBlock' => NULL, @@ -62,7 +62,7 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { 'CRM_Contribute_Form_ContributionPage_Custom' => NULL, 'CRM_Contribute_Form_ContributionPage_Premium' => NULL, 'CRM_Contribute_Form_ContributionPage_Widget' => NULL, - ); + ]; if (!in_array("CiviMember", $config->enableComponents)) { unset($this->_pages['CRM_Member_Form_MembershipBlock']); diff --git a/CRM/Contribute/StateMachine/Search.php b/CRM/Contribute/StateMachine/Search.php index 6d437472e328..62c0d6c07885 100644 --- a/CRM/Contribute/StateMachine/Search.php +++ b/CRM/Contribute/StateMachine/Search.php @@ -1,9 +1,9 @@ _pages = array(); + $this->_pages = []; $this->_pages['CRM_Contribute_Form_Search'] = NULL; list($task, $result) = $this->taskName($controller, 'Search'); @@ -78,7 +78,8 @@ public function __construct($controller, $action = CRM_Core_Action::NONE) { * * @param string $formName * - * @return string + * @return array + * [ 'class' => task classname, 'result' => TRUE ] * the name of the form that will handle the task */ public function taskName($controller, $formName = 'Search') { diff --git a/CRM/Contribute/Task.php b/CRM/Contribute/Task.php index 9c4f20c5ad53..c7679aab1b5b 100644 --- a/CRM/Contribute/Task.php +++ b/CRM/Contribute/Task.php @@ -1,9 +1,9 @@ array( + self::$_tasks = [ + self::TASK_DELETE => [ 'title' => ts('Delete contributions'), 'class' => 'CRM_Contribute_Form_Task_Delete', 'result' => FALSE, - ), - 2 => array( + ], + self::TASK_PRINT => [ 'title' => ts('Print selected rows'), 'class' => 'CRM_Contribute_Form_Task_Print', 'result' => FALSE, - ), - 3 => array( + ], + self::TASK_EXPORT => [ 'title' => ts('Export contributions'), - 'class' => array( + 'class' => [ 'CRM_Export_Form_Select', 'CRM_Export_Form_Map', - ), + ], 'result' => FALSE, - ), - 4 => array( + ], + self::BATCH_UPDATE => [ 'title' => ts('Update multiple contributions'), - 'class' => array( + 'class' => [ 'CRM_Contribute_Form_Task_PickProfile', 'CRM_Contribute_Form_Task_Batch', - ), + ], 'result' => TRUE, - ), - 5 => array( - 'title' => ts('Email - send now'), + ], + self::TASK_EMAIL => [ + 'title' => ts('Email - send now (to %1 or less)', [ + 1 => Civi::settings() + ->get('simple_mail_limit'), + ]), 'class' => 'CRM_Contribute_Form_Task_Email', 'result' => TRUE, - ), - 6 => array( + ], + self::UPDATE_STATUS => [ 'title' => ts('Update pending contribution status'), 'class' => 'CRM_Contribute_Form_Task_Status', 'result' => TRUE, - ), - 7 => array( + ], + self::PDF_RECEIPT => [ 'title' => ts('Receipts - print or email'), 'class' => 'CRM_Contribute_Form_Task_PDF', 'result' => FALSE, - ), - 8 => array( + ], + self::PDF_THANKYOU => [ 'title' => ts('Thank-you letters - print or email'), 'class' => 'CRM_Contribute_Form_Task_PDFLetter', 'result' => FALSE, - ), - 9 => array( + ], + self::PDF_INVOICE => [ 'title' => ts('Invoices - print or email'), 'class' => 'CRM_Contribute_Form_Task_Invoice', 'result' => FALSE, - ), - ); + ], + ]; //CRM-4418, check for delete if (!CRM_Core_Permission::check('delete in CiviContribute')) { - unset(self::$_tasks[1]); + unset(self::$_tasks[self::TASK_DELETE]); } //CRM-12920 - check for edit permission if (!CRM_Core_Permission::check('edit contributions')) { - unset(self::$_tasks[4], self::$_tasks[6]); + unset(self::$_tasks[self::BATCH_UPDATE], self::$_tasks[self::UPDATE_STATUS]); } // remove action "Invoices - print or email" $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); if (!$invoicing) { - unset(self::$_tasks[9]); + unset(self::$_tasks[self::PDF_INVOICE]); } - CRM_Utils_Hook::searchTasks('contribution', self::$_tasks); - asort(self::$_tasks); + parent::tasks(); } return self::$_tasks; } - /** - * These tasks are the core set of task titles - * on contributors - * - * @return array - * the set of task titles - */ - public static function &taskTitles() { - self::tasks(); - $titles = array(); - foreach (self::$_tasks as $id => $value) { - $titles[$id] = $value['title']; - } - return $titles; - } - /** * Show tasks selectively based on the permission level * of the user * * @param int $permission * - * @param bool $softCreditFiltering + * @param array $params + * bool softCreditFiltering: derived from CRM_Contribute_BAO_Query::isSoftCreditOptionEnabled * * @return array * set of tasks that are valid for the user */ - public static function &permissionedTaskTitles($permission, $softCreditFiltering = FALSE) { - $tasks = array(); + public static function permissionedTaskTitles($permission, $params = []) { + if (!isset($params['softCreditFiltering'])) { + $params['softCreditFiltering'] = FALSE; + } if (($permission == CRM_Core_Permission::EDIT) || CRM_Core_Permission::check('edit contributions') ) { $tasks = self::taskTitles(); } else { - $tasks = array( - 3 => self::$_tasks[3]['title'], - 5 => self::$_tasks[5]['title'], - 7 => self::$_tasks[7]['title'], - ); + $tasks = [ + self::TASK_EXPORT => self::$_tasks[self::TASK_EXPORT]['title'], + self::TASK_EMAIL => self::$_tasks[self::TASK_EMAIL]['title'], + self::PDF_RECEIPT => self::$_tasks[self::PDF_RECEIPT]['title'], + ]; //CRM-4418, if (CRM_Core_Permission::check('delete in CiviContribute')) { - $tasks[1] = self::$_tasks[1]['title']; + $tasks[self::TASK_DELETE] = self::$_tasks[self::TASK_DELETE]['title']; } } - if ($softCreditFiltering) { - unset($tasks[4], $tasks[7]); + if ($params['softCreditFiltering']) { + unset($tasks[self::BATCH_UPDATE], $tasks[self::PDF_RECEIPT]); } + + $tasks = parent::corePermissionedTaskTitles($tasks, $permission, $params); return $tasks; } @@ -204,17 +194,9 @@ public static function getTask($value) { self::tasks(); if (!$value || !CRM_Utils_Array::value($value, self::$_tasks)) { // make the print task by default - $value = 2; - } - // this is possible since hooks can inject a task - // CRM-13697 - if (!isset(self::$_tasks[$value]['result'])) { - self::$_tasks[$value]['result'] = NULL; + $value = self::TASK_PRINT; } - return array( - self::$_tasks[$value]['class'], - self::$_tasks[$value]['result'], - ); + return parent::getTask($value); } } diff --git a/CRM/Contribute/Tokens.php b/CRM/Contribute/Tokens.php index 40a5c832a9be..6c97c765dfa8 100644 --- a/CRM/Contribute/Tokens.php +++ b/CRM/Contribute/Tokens.php @@ -2,9 +2,9 @@ /* +--------------------------------------------------------------------+ - | CiviCRM version 4.7 | + | CiviCRM version 5 | +--------------------------------------------------------------------+ - | Copyright CiviCRM LLC (c) 2004-2017 | + | Copyright CiviCRM LLC (c) 2004-2019 | +--------------------------------------------------------------------+ | This file is a part of CiviCRM. | | | @@ -41,7 +41,7 @@ class CRM_Contribute_Tokens extends \Civi\Token\AbstractTokenSubscriber { * @return array */ protected function getPassthruTokens() { - return array( + return [ 'contribution_page_id', 'receive_date', 'total_amount', @@ -50,11 +50,11 @@ protected function getPassthruTokens() { 'trxn_id', 'invoice_id', 'currency', - 'cancel_date', + 'contribution_cancel_date', 'receipt_date', 'thankyou_date', 'tax_amount', - ); + ]; } /** @@ -63,13 +63,14 @@ protected function getPassthruTokens() { * @return array */ protected function getAliasTokens() { - return array( + return [ 'id' => 'contribution_id', 'payment_instrument' => 'payment_instrument_id', 'source' => 'contribution_source', 'status' => 'contribution_status_id', 'type' => 'financial_type_id', - ); + 'cancel_date' => 'contribution_cancel_date', + ]; } /** @@ -85,7 +86,7 @@ public function __construct() { $tokens['source'] = ts('Contribution Source'); $tokens['status'] = ts('Contribution Status'); $tokens['type'] = ts('Financial Type'); - $tokens = array_merge($tokens, $this->getCustomTokens('Contribution')); + $tokens = array_merge($tokens, CRM_Utils_Token::getCustomFieldTokens('Contribution')); parent::__construct('contribution', $tokens); } @@ -97,8 +98,7 @@ public function __construct() { * @return bool */ public function checkActive(\Civi\Token\TokenProcessor $processor) { - return - !empty($processor->context['actionMapping']) + return !empty($processor->context['actionMapping']) && $processor->context['actionMapping']->getEntity() === 'civicrm_contribution'; } @@ -129,7 +129,7 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe $fieldValue = isset($actionSearchResult->{"contrib_$field"}) ? $actionSearchResult->{"contrib_$field"} : NULL; $aliasTokens = $this->getAliasTokens(); - if (in_array($field, array('total_amount', 'fee_amount', 'net_amount'))) { + if (in_array($field, ['total_amount', 'fee_amount', 'net_amount'])) { return $row->format('text/plain')->tokens($entity, $field, \CRM_Utils_Money::format($fieldValue, $actionSearchResult->contrib_currency)); } diff --git a/CRM/Contribute/xml/Menu/Contribute.xml b/CRM/Contribute/xml/Menu/Contribute.xml index 66b318e5f3a5..548902dbc07c 100644 --- a/CRM/Contribute/xml/Menu/Contribute.xml +++ b/CRM/Contribute/xml/Menu/Contribute.xml @@ -323,4 +323,16 @@ CRM_Contribute_Page_AJAX::getSoftContributionRows access CiviCRM + + civicrm/contribute/contributionrecur-payments + Recurring Contribution's Payments + CRM_Contribute_Page_ContributionRecurPayments + access CiviContribute + + + civicrm/membership/recurring-contributions + Membership Recurring Contributions + CRM_Member_Page_RecurringContributions + access CiviContribute + diff --git a/CRM/Core/Action.php b/CRM/Core/Action.php index db7393cd6557..7ce505434d16 100644 --- a/CRM/Core/Action.php +++ b/CRM/Core/Action.php @@ -1,9 +1,9 @@ self::ADD, 'update' => self::UPDATE, 'view' => self::VIEW, @@ -95,14 +95,14 @@ class CRM_Core_Action { 'revert' => self::REVERT, 'close' => self::CLOSE, 'reopen' => self::REOPEN, - ); + ]; /** * The flipped version of the names array, initialized when used * * @var array */ - static $_description; + public static $_description; /** * Called by the request object to translate a string into a mask. @@ -216,7 +216,7 @@ public static function formLink( // make links indexed sequentially instead of by bitmask // otherwise it's next to impossible to reliably add new ones - $seqLinks = array(); + $seqLinks = []; foreach ($links as $bit => $link) { $link['bit'] = $bit; $seqLinks[] = $link; @@ -226,7 +226,7 @@ public static function formLink( CRM_Utils_Hook::links($op, $objectName, $objectId, $seqLinks, $mask, $values); } - $url = array(); + $url = []; foreach ($seqLinks as $i => $link) { if (!$mask || !array_key_exists('bit', $link) || ($mask & $link['bit'])) { @@ -236,7 +236,7 @@ public static function formLink( if (isset($link['qs']) && !CRM_Utils_System::isNull($link['qs'])) { $urlPath = CRM_Utils_System::url(self::replace($link['url'], $values), - self::replace($link['qs'], $values), TRUE, NULL, TRUE, $frontend + self::replace($link['qs'], $values), FALSE, NULL, TRUE, $frontend ); } else { @@ -298,6 +298,65 @@ public static function formLink( return $result; } + /** + * Given a set of links and a mask, return a filtered (by mask) array containing the final links with parsed values + * and calling hooks as appropriate. + * Use this when passing a set of action links to the API or to the form without adding html formatting. + * + * @param array $links + * The set of link items. + * @param int $mask + * The mask to be used. a null mask means all items. + * @param array $values + * The array of values for parameter substitution in the link items. + * @param null $op + * @param null $objectName + * @param int $objectId + * + * @return array|null + * The array describing each link + */ + public static function filterLinks( + $links, + $mask, + $values, + $op = NULL, + $objectName = NULL, + $objectId = NULL + ) { + if (empty($links)) { + return NULL; + } + + // make links indexed sequentially instead of by bitmask + // otherwise it's next to impossible to reliably add new ones + $seqLinks = array(); + foreach ($links as $bit => $link) { + $link['bit'] = $bit; + $seqLinks[] = $link; + } + + if ($op && $objectName && $objectId) { + CRM_Utils_Hook::links($op, $objectName, $objectId, $seqLinks, $mask, $values); + } + + foreach ($seqLinks as $i => $link) { + if (!$mask || !array_key_exists('bit', $link) || ($mask & $link['bit'])) { + $seqLinks[$i]['extra'] = isset($link['extra']) ? self::replace($link['extra'], $values) : NULL; + + if (isset($link['qs']) && !CRM_Utils_System::isNull($link['qs'])) { + $seqLinks[$i]['url'] = self::replace($link['url'], $values); + $seqLinks[$i]['qs'] = self::replace($link['qs'], $values); + } + } + else { + unset($seqLinks[$i]); + } + } + + return $seqLinks; + } + /** * Given a string and an array of values, substitute the real values * in the placeholder in the str in the CiviCRM format diff --git a/CRM/Core/BAO/ActionLog.php b/CRM/Core/BAO/ActionLog.php index 91538012dc78..2d8532e1f9e6 100644 --- a/CRM/Core/BAO/ActionLog.php +++ b/CRM/Core/BAO/ActionLog.php @@ -1,9 +1,9 @@ $_action_mapping[$filters['id']], - ); + ]; } else { throw new CRM_Core_Exception("getMappings() called with unsupported filter: " . implode(', ', array_keys($filters))); @@ -84,11 +84,11 @@ public static function getMapping($id) { * @throws CRM_Core_Exception */ public static function getAllEntityValueLabels() { - $entityValueLabels = array(); + $entityValueLabels = []; foreach (CRM_Core_BAO_ActionSchedule::getMappings() as $mapping) { /** @var \Civi\ActionSchedule\Mapping $mapping */ $entityValueLabels[$mapping->getId()] = $mapping->getValueLabels(); - $valueLabel = array('- ' . strtolower($mapping->getValueHeader()) . ' -'); + $valueLabel = ['- ' . strtolower($mapping->getValueHeader()) . ' -']; $entityValueLabels[$mapping->getId()] = $valueLabel + $entityValueLabels[$mapping->getId()]; } return $entityValueLabels; @@ -102,10 +102,10 @@ public static function getAllEntityValueLabels() { */ public static function getAllEntityStatusLabels() { $entityValueLabels = self::getAllEntityValueLabels(); - $entityStatusLabels = array(); + $entityStatusLabels = []; foreach (CRM_Core_BAO_ActionSchedule::getMappings() as $mapping) { /** @var \Civi\ActionSchedule\Mapping $mapping */ - $statusLabel = array('- ' . strtolower($mapping->getStatusHeader()) . ' -'); + $statusLabel = ['- ' . strtolower($mapping->getStatusHeader()) . ' -']; $entityStatusLabels[$mapping->getId()] = $entityValueLabels[$mapping->getId()]; foreach ($entityStatusLabels[$mapping->getId()] as $kkey => & $vval) { $vval = $statusLabel + $mapping->getStatusLabels($kkey); @@ -120,13 +120,14 @@ public static function getAllEntityStatusLabels() { * @param bool $namesOnly * Return simple list of names. * - * @param \Civi\ActionSchedule\Mapping|NULL $filterMapping + * @param \Civi\ActionSchedule\Mapping|null $filterMapping * Filter by the schedule's mapping type. * @param int $filterValue * Filter by the schedule's entity_value. * * @return array * (reference) reminder list + * @throws \CRM_Core_Exception */ public static function &getList($namesOnly = FALSE, $filterMapping = NULL, $filterValue = NULL) { $query = " @@ -146,21 +147,21 @@ public static function &getList($namesOnly = FALSE, $filterMapping = NULL, $filt FROM civicrm_action_schedule cas "; - $queryParams = array(); + $queryParams = []; $where = " WHERE 1 "; if ($filterMapping and $filterValue) { $where .= " AND cas.entity_value = %1 AND cas.mapping_id = %2"; - $queryParams[1] = array($filterValue, 'Integer'); - $queryParams[2] = array($filterMapping->getId(), 'String'); + $queryParams[1] = [$filterValue, 'Integer']; + $queryParams[2] = [$filterMapping->getId(), 'String']; } $where .= " AND cas.used_for IS NULL"; $query .= $where; $dao = CRM_Core_DAO::executeQuery($query, $queryParams); while ($dao->fetch()) { /** @var Civi\ActionSchedule\Mapping $filterMapping */ - $filterMapping = CRM_Utils_Array::first(self::getMappings(array( + $filterMapping = CRM_Utils_Array::first(self::getMappings([ 'id' => $dao->mapping_id, - ))); + ])); $list[$dao->id]['id'] = $dao->id; $list[$dao->id]['title'] = $dao->title; $list[$dao->id]['start_action_offset'] = $dao->start_action_offset; @@ -194,7 +195,7 @@ public static function &getList($namesOnly = FALSE, $filterMapping = NULL, $filt * * @return CRM_Core_DAO_ActionSchedule */ - public static function add(&$params, $ids = array()) { + public static function add(&$params, $ids = []) { $actionSchedule = new CRM_Core_DAO_ActionSchedule(); $actionSchedule->copyValues($params); @@ -259,8 +260,8 @@ public static function del($id) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_ActionSchedule', $id, 'is_active', $is_active); @@ -273,9 +274,9 @@ public static function setIsActive($id, $is_active) { * @throws CRM_Core_Exception */ public static function sendMailings($mappingID, $now) { - $mapping = CRM_Utils_Array::first(self::getMappings(array( + $mapping = CRM_Utils_Array::first(self::getMappings([ 'id' => $mappingID, - ))); + ])); $actionSchedule = new CRM_Core_DAO_ActionSchedule(); $actionSchedule->mapping_id = $mappingID; @@ -285,7 +286,7 @@ public static function sendMailings($mappingID, $now) { while ($actionSchedule->fetch()) { $query = CRM_Core_BAO_ActionSchedule::prepareMailingQuery($mapping, $actionSchedule); $dao = CRM_Core_DAO::executeQuery($query, - array(1 => array($actionSchedule->id, 'Integer')) + [1 => [$actionSchedule->id, 'Integer']] ); $multilingual = CRM_Core_I18n::isMultilingual(); @@ -296,7 +297,7 @@ public static function sendMailings($mappingID, $now) { CRM_Core_BAO_ActionSchedule::setCommunicationLanguage($actionSchedule->communication_language, $preferred_language); } - $errors = array(); + $errors = []; try { $tokenProcessor = self::createTokenProcessor($actionSchedule, $mapping); $tokenProcessor->addRow() @@ -322,16 +323,15 @@ public static function sendMailings($mappingID, $now) { } // update action log record - $logParams = array( + $logParams = [ 'id' => $dao->reminderID, 'is_error' => !empty($errors), 'message' => empty($errors) ? "null" : implode(' ', $errors), 'action_date_time' => $now, - ); + ]; CRM_Core_BAO_ActionLog::create($logParams); } - $dao->free(); } } @@ -342,7 +342,7 @@ public static function sendMailings($mappingID, $now) { * * @throws API_Exception */ - public static function buildRecipientContacts($mappingID, $now, $params = array()) { + public static function buildRecipientContacts($mappingID, $now, $params = []) { $actionSchedule = new CRM_Core_DAO_ActionSchedule(); $actionSchedule->mapping_id = $mappingID; $actionSchedule->is_active = 1; @@ -353,9 +353,9 @@ public static function buildRecipientContacts($mappingID, $now, $params = array( while ($actionSchedule->fetch()) { /** @var \Civi\ActionSchedule\Mapping $mapping */ - $mapping = CRM_Utils_Array::first(self::getMappings(array( + $mapping = CRM_Utils_Array::first(self::getMappings([ 'id' => $mappingID, - ))); + ])); $builder = new \Civi\ActionSchedule\RecipientBuilder($now, $actionSchedule, $mapping); $builder->build(); } @@ -367,7 +367,7 @@ public static function buildRecipientContacts($mappingID, $now, $params = array( * * @return array */ - public static function processQueue($now = NULL, $params = array()) { + public static function processQueue($now = NULL, $params = []) { $now = $now ? CRM_Utils_Time::setTime($now) : CRM_Utils_Time::getTime(); $mappings = CRM_Core_BAO_ActionSchedule::getMappings(); @@ -376,10 +376,10 @@ public static function processQueue($now = NULL, $params = array()) { CRM_Core_BAO_ActionSchedule::sendMailings($mappingID, $now); } - $result = array( + $result = [ 'is_error' => 0, 'messages' => ts('Sent all scheduled reminders successfully'), - ); + ]; return $result; } @@ -394,10 +394,10 @@ public static function isConfigured($id, $mappingID) { WHERE mapping_id = %1 AND entity_value = %2"; - $params = array( - 1 => array($mappingID, 'String'), - 2 => array($id, 'Integer'), - ); + $params = [ + 1 => [$mappingID, 'String'], + 2 => [$id, 'Integer'], + ]; return CRM_Core_DAO::singleValueQuery($queryString, $params); } @@ -409,13 +409,13 @@ public static function isConfigured($id, $mappingID) { */ public static function getRecipientListing($mappingID, $recipientType) { if (!$mappingID) { - return array(); + return []; } /** @var \Civi\ActionSchedule\Mapping $mapping */ - $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings(array( + $mapping = CRM_Utils_Array::first(CRM_Core_BAO_ActionSchedule::getMappings([ 'id' => $mappingID, - ))); + ])); return $mapping->getRecipientListing($recipientType); } @@ -458,7 +458,7 @@ public static function setCommunicationLanguage($communication_language, $prefer * @param Civi\ActionSchedule\Mapping $mapping * @param int $contactID * @param int $entityID - * @param int|NULL $caseID + * @param int|null $caseID * @throws CRM_Core_Exception */ protected static function createMailingActivity($tokenRow, $mapping, $contactID, $entityID, $caseID) { @@ -475,7 +475,7 @@ protected static function createMailingActivity($tokenRow, $mapping, $contactID, = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Reminder Sent'); } - $activityParams = array( + $activityParams = [ 'subject' => $tokenRow->render('subject'), 'details' => $tokenRow->render('body_html'), 'source_contact_id' => $session->get('userID') ? $session->get('userID') : $contactID, @@ -486,13 +486,13 @@ protected static function createMailingActivity($tokenRow, $mapping, $contactID, 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'), 'activity_type_id' => $activityTypeID, 'source_record_id' => $entityID, - ); + ]; // @todo use api, remove all the above wrangling $activity = CRM_Activity_BAO_Activity::create($activityParams); //file reminder on case if source activity is a case activity if (!empty($caseID)) { - $caseActivityParams = array(); + $caseActivityParams = []; $caseActivityParams['case_id'] = $caseID; $caseActivityParams['activity_id'] = $activity->id; CRM_Case_BAO_Case::processCaseActivity($caseActivityParams); @@ -511,13 +511,13 @@ protected static function prepareMailingQuery($mapping, $actionSchedule) { ->select("e.id as entityID, e.*") ->where("reminder.action_schedule_id = #casActionScheduleId") ->where("reminder.action_date_time IS NULL") - ->param(array( + ->param([ 'casActionScheduleId' => $actionSchedule->id, 'casMailingJoinType' => ($actionSchedule->limit_to == 0) ? 'LEFT JOIN' : 'INNER JOIN', 'casMappingId' => $mapping->getId(), 'casMappingEntity' => $mapping->getEntity(), 'casEntityJoinExpr' => 'e.id = reminder.entity_id', - )); + ]); if ($actionSchedule->limit_to == 0) { $select->where("e.id = reminder.entity_id OR reminder.entity_table = 'civicrm_contact'"); @@ -543,7 +543,13 @@ protected static function prepareMailingQuery($mapping, $actionSchedule) { protected static function sendReminderSms($tokenRow, $schedule, $toContactID) { $toPhoneNumber = self::pickSmsPhoneNumber($toContactID); if (!$toPhoneNumber) { - return array("sms_phone_missing" => "Couldn't find recipient's phone number."); + return ["sms_phone_missing" => "Couldn't find recipient's phone number."]; + } + + // dev/core#369 If an SMS provider is deleted then the relevant row in the action_schedule_table is set to NULL + // So we need to exclude them. + if (CRM_Utils_System::isNull($schedule->sms_provider_id)) { + return ["sms_provider_missing" => "SMS reminder cannot be sent because the SMS provider has been deleted."]; } $messageSubject = $tokenRow->render('subject'); @@ -551,23 +557,20 @@ protected static function sendReminderSms($tokenRow, $schedule, $toContactID) { $session = CRM_Core_Session::singleton(); $userID = $session->get('userID') ? $session->get('userID') : $tokenRow->context['contactId']; - $smsParams = array( + $smsParams = [ 'To' => $toPhoneNumber, 'provider_id' => $schedule->sms_provider_id, 'activity_subject' => $messageSubject, - ); - $activityTypeID = CRM_Core_OptionGroup::getValue('activity_type', - 'SMS', - 'name' - ); - $activityParams = array( + ]; + $activityTypeID = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS'); + $activityParams = [ 'source_contact_id' => $userID, 'activity_type_id' => $activityTypeID, 'activity_date_time' => date('YmdHis'), 'subject' => $messageSubject, 'details' => $sms_body_text, - 'status_id' => CRM_Core_OptionGroup::getValue('activity_status', 'Completed', 'name'), - ); + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Completed'), + ]; $activity = CRM_Activity_BAO_Activity::create($activityParams); @@ -578,7 +581,7 @@ protected static function sendReminderSms($tokenRow, $schedule, $toContactID) { $userID ); - return array(); + return []; } /** @@ -606,7 +609,7 @@ protected static function pickFromEmail($actionSchedule) { protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) { $toEmail = CRM_Contact_BAO_Contact::getPrimaryEmail($toContactID); if (!$toEmail) { - return array("email_missing" => "Couldn't find recipient's email address."); + return ["email_missing" => "Couldn't find recipient's email address."]; } $body_text = $tokenRow->render('body_text'); @@ -616,7 +619,7 @@ protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) } // set up the parameters for CRM_Utils_Mail::send - $mailParams = array( + $mailParams = [ 'groupName' => 'Scheduled Reminder Sender', 'from' => self::pickFromEmail($schedule), 'toName' => $tokenRow->context['contact']['display_name'], @@ -624,7 +627,7 @@ protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) 'subject' => $tokenRow->render('subject'), 'entity' => 'action_schedule', 'entity_id' => $schedule->id, - ); + ]; if (!$body_html || $tokenRow->context['contact']['preferred_mail_format'] == 'Text' || $tokenRow->context['contact']['preferred_mail_format'] == 'Both' @@ -640,10 +643,10 @@ protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) } $result = CRM_Utils_Mail::send($mailParams); if (!$result || is_a($result, 'PEAR_Error')) { - return array('email_fail' => 'Failed to send message'); + return ['email_fail' => 'Failed to send message']; } - return array(); + return []; } /** @@ -652,12 +655,12 @@ protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) * @return \Civi\Token\TokenProcessor */ protected static function createTokenProcessor($schedule, $mapping) { - $tp = new \Civi\Token\TokenProcessor(\Civi\Core\Container::singleton()->get('dispatcher'), array( + $tp = new \Civi\Token\TokenProcessor(\Civi\Core\Container::singleton()->get('dispatcher'), [ 'controller' => __CLASS__, 'actionSchedule' => $schedule, 'actionMapping' => $mapping, 'smarty' => TRUE, - )); + ]); $tp->addMessage('body_text', $schedule->body_text, 'text/plain'); $tp->addMessage('body_html', $schedule->body_html, 'text/html'); $tp->addMessage('sms_body_text', $schedule->sms_body_text, 'text/plain'); @@ -673,11 +676,11 @@ protected static function createTokenProcessor($schedule, $mapping) { * @return NULL|string */ protected static function pickSmsPhoneNumber($smsToContactId) { - $toPhoneNumbers = CRM_Core_BAO_Phone::allPhones($smsToContactId, FALSE, 'Mobile', array( + $toPhoneNumbers = CRM_Core_BAO_Phone::allPhones($smsToContactId, FALSE, 'Mobile', [ 'is_deceased' => 0, 'is_deleted' => 0, 'do_not_sms' => 0, - )); + ]); //to get primary mobile ph,if not get a first mobile phONE if (!empty($toPhoneNumbers)) { $toPhoneNumberDetails = reset($toPhoneNumbers); @@ -694,10 +697,10 @@ protected static function pickSmsPhoneNumber($smsToContactId) { * array(mixed $value => string $label). */ public static function getAdditionalRecipients() { - return array( + return [ 'manual' => ts('Choose Recipient(s)'), 'group' => ts('Select Group'), - ); + ]; } } diff --git a/CRM/Core/BAO/Address.php b/CRM/Core/BAO/Address.php index 032ecee9a15b..af3dbd233da1 100644 --- a/CRM/Core/BAO/Address.php +++ b/CRM/Core/BAO/Address.php @@ -1,9 +1,9 @@ $params['entity_table'], 'entity_id' => $params['entity_id'], - ); + ]; $addresses = self::allEntityAddress($entityElements); } $isPrimary = $isBilling = TRUE; - $blocks = array(); + $blocks = []; foreach ($params['address'] as $key => $value) { if (!is_array($value)) { continue; @@ -92,7 +92,7 @@ public static function create(&$params, $fixAddress = TRUE, $entity = NULL) { // $updateBlankLocInfo will help take appropriate decision. CRM-5969 if (isset($value['id']) && !$addressExists && $updateBlankLocInfo) { //delete the existing record - CRM_Core_BAO_Block::blockDelete('Address', array('id' => $value['id'])); + CRM_Core_BAO_Block::blockDelete('Address', ['id' => $value['id']]); continue; } elseif (!$addressExists) { @@ -134,7 +134,7 @@ public static function create(&$params, $fixAddress = TRUE, $entity = NULL) { * * @return CRM_Core_BAO_Address|null */ - public static function add(&$params, $fixAddress) { + public static function add(&$params, $fixAddress = FALSE) { $address = new CRM_Core_DAO_Address(); $checkPermissions = isset($params['check_permissions']) ? $params['check_permissions'] : TRUE; @@ -152,34 +152,40 @@ public static function add(&$params, $fixAddress) { CRM_Core_BAO_Block::handlePrimary($params, get_class()); } + // (prevent chaining 1 and 3) CRM-21214 + if (isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) { + self::fixSharedAddress($params); + } + $address->copyValues($params); $address->save(); if ($address->id) { - $customFields = CRM_Core_BAO_CustomField::getFields('Address', FALSE, TRUE, NULL, NULL, FALSE, FALSE, $checkPermissions); - - if (!empty($customFields)) { - $addressCustom = CRM_Core_BAO_CustomField::postProcess($params, - $address->id, - 'Address', - FALSE, - $checkPermissions - ); + if (isset($params['custom'])) { + $addressCustom = $params['custom']; + } + else { + $customFields = CRM_Core_BAO_CustomField::getFields('Address', FALSE, TRUE, NULL, NULL, FALSE, FALSE, $checkPermissions); + + if (!empty($customFields)) { + $addressCustom = CRM_Core_BAO_CustomField::postProcess($params, + $address->id, + 'Address', + FALSE, + $checkPermissions + ); + } } if (!empty($addressCustom)) { CRM_Core_BAO_CustomValueTable::store($addressCustom, 'civicrm_address', $address->id); } - //call the function to sync shared address + // call the function to sync shared address and create relationships + // if address is already shared, share master_id with all children and update relationships accordingly + // (prevent chaining 2) CRM-21214 self::processSharedAddress($address->id, $params); - // call the function to create shared relationships - // we only create create relationship if address is shared by Individual - if (!CRM_Utils_System::isNull($address->master_id)) { - self::processSharedAddressRelationship($address->master_id, $params); - } - // lets call the post hook only after we've done all the follow on processing CRM_Utils_Hook::post($hook, 'Address', $address->id, $address); } @@ -197,7 +203,7 @@ public static function fixAddress(&$params) { if (!empty($params['billing_street_address'])) { //Check address is coming from online contribution / registration page //Fixed :CRM-5076 - $billing = array( + $billing = [ 'street_address' => 'billing_street_address', 'city' => 'billing_city', 'postal_code' => 'billing_postal_code', @@ -205,7 +211,7 @@ public static function fixAddress(&$params) { 'state_province_id' => 'billing_state_province_id', 'country' => 'billing_country', 'country_id' => 'billing_country_id', - ); + ]; foreach ($billing as $key => $val) { if ($value = CRM_Utils_Array::value($val, $params)) { @@ -341,51 +347,41 @@ public static function fixAddress(&$params) { $params['country'] = CRM_Core_PseudoConstant::country($params['country_id']); } - $config = CRM_Core_Config::singleton(); - $asp = Civi::settings()->get('address_standardization_provider'); // clean up the address via USPS web services if enabled if ($asp === 'USPS' && $params['country_id'] == 1228 ) { CRM_Utils_Address_USPS::checkAddress($params); + } + // do street parsing again if enabled, since street address might have changed + $parseStreetAddress = CRM_Utils_Array::value( + 'street_address_parsing', + CRM_Core_BAO_Setting::valueOptions( + CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, + 'address_options' + ), + FALSE + ); - // do street parsing again if enabled, since street address might have changed - $parseStreetAddress = CRM_Utils_Array::value( - 'street_address_parsing', - CRM_Core_BAO_Setting::valueOptions( - CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, - 'address_options' - ), - FALSE - ); - - if ($parseStreetAddress && !empty($params['street_address'])) { - foreach (array( - 'street_number', - 'street_name', - 'street_unit', - 'street_number_suffix', - ) as $fld) { - unset($params[$fld]); - } - // main parse string. - $parseString = CRM_Utils_Array::value('street_address', $params); - $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString); - - // merge parse address in to main address block. - $params = array_merge($params, $parsedFields); + if ($parseStreetAddress && !empty($params['street_address'])) { + foreach (['street_number', 'street_name', 'street_unit', 'street_number_suffix'] as $fld) { + unset($params[$fld]); } - } + // main parse string. + $parseString = CRM_Utils_Array::value('street_address', $params); + $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString); - // check if geocode should be skipped (can be forced with an optional parameter through the api) - $skip_geocode = (isset($params['skip_geocode']) && $params['skip_geocode']) ? TRUE : FALSE; + // merge parse address in to main address block. + $params = array_merge($params, $parsedFields); + } - // add latitude and longitude and format address if needed - if (!$skip_geocode && !empty($config->geocodeMethod) && ($config->geocodeMethod != 'CRM_Utils_Geocode_OpenStreetMaps') && empty($params['manual_geo_code'])) { - $class = $config->geocodeMethod; - $class::format($params); + // skip_geocode is an optional parameter through the api. + // manual_geo_code is on the contact edit form. They do the same thing.... + if (empty($params['skip_geocode']) && empty($params['manual_geo_code'])) { + self::addGeocoderData($params); } + } /** @@ -404,7 +400,7 @@ public static function dataExists(&$params) { $config = CRM_Core_Config::singleton(); foreach ($params as $name => $value) { - if (in_array($name, array( + if (in_array($name, [ 'is_primary', 'location_type_id', 'id', @@ -412,7 +408,7 @@ public static function dataExists(&$params) { 'is_billing', 'display', 'master_id', - ))) { + ])) { continue; } elseif (!CRM_Utils_System::isNull($value)) { @@ -420,9 +416,9 @@ public static function dataExists(&$params) { if (substr($name, 0, 7) == 'country') { // make sure its different from the default country // iso code - $defaultCountry = $config->defaultContactCountry(); + $defaultCountry = CRM_Core_BAO_Country::defaultContactCountry(); // full name - $defaultCountryName = $config->defaultContactCountryName(); + $defaultCountryName = CRM_Core_BAO_Country::defaultContactCountryName(); if ($defaultCountry) { if ($value == $defaultCountry || @@ -466,14 +462,14 @@ public static function &getValues($entityBlock, $microformat = FALSE, $fieldName if (empty($entityBlock)) { return NULL; } - $addresses = array(); + $addresses = []; $address = new CRM_Core_BAO_Address(); if (empty($entityBlock['entity_table'])) { $address->$fieldName = CRM_Utils_Array::value($fieldName, $entityBlock); } else { - $addressIds = array(); + $addressIds = []; $addressIds = self::allEntityAddress($entityBlock); if (!empty($addressIds[1])) { @@ -498,19 +494,14 @@ public static function &getValues($entityBlock, $microformat = FALSE, $fieldName while ($address->fetch()) { // deprecate reference. if ($count > 1) { - foreach (array( - 'state', - 'state_name', - 'country', - 'world_region', - ) as $fld) { + foreach (['state', 'state_name', 'country', 'world_region'] as $fld) { if (isset($address->$fld)) { unset($address->$fld); } } } $stree = $address->street_address; - $values = array(); + $values = []; CRM_Core_DAO::storeValues($address, $values); // add state and country information: CRM-369 @@ -536,16 +527,16 @@ public static function &getValues($entityBlock, $microformat = FALSE, $fieldName $values['display'] = $address->display; $values['display_text'] = $address->display_text; - if (is_numeric($address->master_id)) { + if (isset($address->master_id) && !CRM_Utils_System::isNull($address->master_id)) { $values['use_shared_address'] = 1; } $addresses[$count] = $values; - //unset is_primary after first block. Due to some bug in earlier version - //there might be more than one primary blocks, hence unset is_primary other than first + //There should never be more than one primary blocks, hence set is_primary = 0 other than first + // Calling functions expect the key is_primary to be set, so do not unset it here! if ($count > 1) { - unset($addresses[$count]['is_primary']); + $addresses[$count]['is_primary'] = 0; } $count++; @@ -561,7 +552,7 @@ public static function &getValues($entityBlock, $microformat = FALSE, $fieldName * Unexplained parameter that I've always wondered about. */ public function addDisplay($microformat = FALSE) { - $fields = array( + $fields = [ // added this for CRM 1200 'address_id' => $this->id, // CRM-4003 @@ -577,7 +568,7 @@ public function addDisplay($microformat = FALSE) { 'postal_code_suffix' => isset($this->postal_code_suffix) ? $this->postal_code_suffix : "", 'country' => isset($this->country) ? $this->country : "", 'world_region' => isset($this->world_region) ? $this->world_region : "", - ); + ]; if (isset($this->county_id) && $this->county_id) { $fields['county'] = CRM_Core_PseudoConstant::county($this->county_id); @@ -611,9 +602,9 @@ public static function allAddress($id, $updateBlankLocInfo = FALSE) { FROM civicrm_contact, civicrm_address WHERE civicrm_address.contact_id = civicrm_contact.id AND civicrm_contact.id = %1 ORDER BY civicrm_address.is_primary DESC, address_id ASC"; - $params = array(1 => array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; - $addresses = array(); + $addresses = []; $dao = CRM_Core_DAO::executeQuery($query, $params); $count = 1; while ($dao->fetch()) { @@ -638,7 +629,7 @@ public static function allAddress($id, $updateBlankLocInfo = FALSE) { * the array of adrress data */ public static function allEntityAddress(&$entityElements) { - $addresses = array(); + $addresses = []; if (empty($entityElements)) { return $addresses; } @@ -655,7 +646,7 @@ public static function allEntityAddress(&$entityElements) { AND ltype.id = civicrm_address.location_type_id ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC, address_id ASC "; - $params = array(1 => array($entityId, 'Integer')); + $params = [1 => [$entityId, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($sql, $params); $locationCount = 1; while ($dao->fetch()) { @@ -672,27 +663,26 @@ public static function allEntityAddress(&$entityElements) { * Array of address sequence. */ public static function addressSequence() { - $config = CRM_Core_Config::singleton(); - $addressSequence = $config->addressSequence(); + $addressSequence = CRM_Utils_Address::sequence(\Civi::settings()->get('address_format')); $countryState = $cityPostal = FALSE; foreach ($addressSequence as $key => $field) { if ( - in_array($field, array('country', 'state_province')) && + in_array($field, ['country', 'state_province']) && !$countryState ) { $countryState = TRUE; $addressSequence[$key] = 'country_state_province'; } elseif ( - in_array($field, array('city', 'postal_code')) && + in_array($field, ['city', 'postal_code']) && !$cityPostal ) { $cityPostal = TRUE; $addressSequence[$key] = 'city_postal_code'; } elseif ( - in_array($field, array('country', 'state_province', 'city', 'postal_code')) + in_array($field, ['country', 'state_province', 'city', 'postal_code']) ) { unset($addressSequence[$key]); } @@ -718,31 +708,17 @@ public static function addressSequence() { * parsed fields values. */ public static function parseStreetAddress($streetAddress, $locale = NULL) { - $config = CRM_Core_Config::singleton(); - - /* locales supported include: - * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf - * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp - * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp - * NB: common use of comma after street number also supported - * default is en_US - */ - - $supportedLocalesForParsing = array('en_US', 'en_CA', 'fr_CA'); - if (!$locale) { - $locale = $config->lcMessages; - } - // as different locale explicitly requested but is not available, display warning message and set $locale = 'en_US' - if (!in_array($locale, $supportedLocalesForParsing)) { - CRM_Core_Session::setStatus(ts('Unsupported locale specified to parseStreetAddress: %1. Proceeding with en_US locale.', array(1 => $locale)), ts('Unsupported Locale'), 'alert'); + // use 'en_US' for address parsing if the requested locale is not supported. + if (!self::isSupportedParsingLocale($locale)) { $locale = 'en_US'; } - $emptyParseFields = $parseFields = array( + + $emptyParseFields = $parseFields = [ 'street_name' => '', 'street_unit' => '', 'street_number' => '', 'street_number_suffix' => '', - ); + ]; if (empty($streetAddress)) { return $parseFields; @@ -750,11 +726,9 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { $streetAddress = trim($streetAddress); - $matches = array(); - if (in_array($locale, array( - 'en_CA', - 'fr_CA', - )) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches) + $matches = []; + if (in_array($locale, ['en_CA', 'fr_CA']) + && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches) ) { $parseFields['street_unit'] = $matches[1]; // unset from rest of street address @@ -762,7 +736,7 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { } // get street number and suffix. - $matches = array(); + $matches = []; //alter street number/suffix handling so that we accept -digit if (preg_match('/^[A-Za-z0-9]+([\S]+)/', $streetAddress, $matches)) { // check that $matches[0] is numeric, else assume no street number @@ -770,7 +744,7 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { $streetNumAndSuffix = $matches[0]; // get street number. - $matches = array(); + $matches = []; if (preg_match('/^(\d+)/', $streetNumAndSuffix, $matches)) { $parseFields['street_number'] = $matches[0]; $suffix = preg_replace('/^(\d+)/', '', $streetNumAndSuffix); @@ -789,8 +763,13 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { $streetAddress = trim($streetAddress); } + // If street number is too large, we cannot store it. + if ($parseFields['street_number'] > CRM_Utils_Type::INT_MAX) { + return $emptyParseFields; + } + // suffix might be like 1/2 - $matches = array(); + $matches = []; if (preg_match('/^\d\/\d/', $streetAddress, $matches)) { $parseFields['street_number_suffix'] .= $matches[0]; @@ -801,7 +780,7 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { // now get the street unit. // supportable street unit formats. - $streetUnitFormats = array( + $streetUnitFormats = [ 'APT', 'APARTMENT', 'BSMT', @@ -842,19 +821,19 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { 'SUITE', 'UNIT', '#', - ); + ]; // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale - if (in_array($locale, array( + if (in_array($locale, [ 'en_CA', 'fr_CA', - ))) { - $streetUnitFormats = array('APT', 'APP', 'SUITE', 'BUREAU', 'UNIT'); + ])) { + $streetUnitFormats = ['APT', 'APP', 'SUITE', 'BUREAU', 'UNIT']; } //@todo per CRM-14459 this regex picks up words with the string in them - e.g APT picks up //Captain - presuming fixing regex (& adding test) to ensure a-z does not preced string will fix $streetUnitPreg = '/(' . implode('|\s', $streetUnitFormats) . ')(.+)?/i'; - $matches = array(); + $matches = []; if (preg_match($streetUnitPreg, $streetAddress, $matches)) { $parseFields['street_unit'] = trim($matches[0]); $streetAddress = str_replace($matches[0], '', $streetAddress); @@ -880,6 +859,38 @@ public static function parseStreetAddress($streetAddress, $locale = NULL) { return $parseFields; } + /** + * Determines if the specified locale is + * supported by address parsing. + * If no locale is specified then it + * will check the default configured locale. + * + * locales supported include: + * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf + * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp + * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp + * NB: common use of comma after street number also supported + * + * @param string $locale + * The locale to be checked + * + * @return bool + */ + public static function isSupportedParsingLocale($locale = NULL) { + if (!$locale) { + $config = CRM_Core_Config::singleton(); + $locale = $config->lcMessages; + } + + $parsingSupportedLocales = ['en_US', 'en_CA', 'fr_CA']; + + if (in_array($locale, $parsingSupportedLocales)) { + return TRUE; + } + + return FALSE; + } + /** * Validate the address fields based on the address options enabled. * in the Address Settings @@ -920,7 +931,7 @@ public static function validateAddressOptions($fields) { */ public static function checkContactSharedAddress($addressId) { $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1'; - return CRM_Core_DAO::singleValueQuery($query, array(1 => array($addressId, 'Integer'))); + return CRM_Core_DAO::singleValueQuery($query, [1 => [$addressId, 'Integer']]); } /** @@ -937,7 +948,7 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { return; } - $sharedLocations = array(); + $sharedLocations = []; $query = " SELECT is_primary, @@ -946,7 +957,7 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { WHERE contact_id = %1 AND master_id IS NOT NULL"; - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($contactId, 'Positive'))); + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$contactId, 'Positive']]); while ($dao->fetch()) { $sharedLocations[$dao->location_type_id] = $dao->location_type_id; if ($dao->is_primary) { @@ -959,7 +970,7 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { return; } - $addressFields = array( + $addressFields = [ 'city', 'county', 'country', @@ -973,7 +984,7 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { 'supplemental_address_1', 'supplemental_address_2', 'supplemental_address_3', - ); + ]; foreach ($fields as $name => & $values) { if (!is_array($values) || empty($values)) { @@ -995,6 +1006,27 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { } } + /** + * Fix the shared address if address is already shared + * or if address will be shared with itself. + * + * @param array $params + * Associated array of address params. + */ + public static function fixSharedAddress(&$params) { + // if address master address is shared, use its master (prevent chaining 1) CRM-21214 + $masterMasterId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Address', $params['master_id'], 'master_id'); + if ($masterMasterId > 0) { + $params['master_id'] = $masterMasterId; + } + + // prevent an endless chain between two shared addresses (prevent chaining 3) CRM-21214 + if (CRM_Utils_Array::value('id', $params) == $params['master_id']) { + $params['master_id'] = NULL; + CRM_Core_Session::setStatus(ts("You can't connect an address to itself"), '', 'warning'); + } + } + /** * Update the shared addresses if master address is modified. * @@ -1004,21 +1036,37 @@ public static function checkContactSharedAddressFields(&$fields, $contactId) { * Associated array of address params. */ public static function processSharedAddress($addressId, $params) { - $query = 'SELECT id FROM civicrm_address WHERE master_id = %1'; - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($addressId, 'Integer'))); + $query = 'SELECT id, contact_id FROM civicrm_address WHERE master_id = %1'; + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$addressId, 'Integer']]); + + // Default to TRUE if not set to maintain api backward compatibility. + $createRelationship = isset($params['update_current_employer']) ? $params['update_current_employer'] : TRUE; // unset contact id - $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id', 'contact_id'); + $skipFields = ['is_primary', 'location_type_id', 'is_billing', 'contact_id']; + if (isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) { + if ($createRelationship) { + // call the function to create a relationship for the new shared address + self::processSharedAddressRelationship($params['master_id'], $params['contact_id']); + } + } + else { + // else no new shares will be created, only update shared addresses + $skipFields[] = 'master_id'; + } foreach ($skipFields as $value) { unset($params[$value]); } $addressDAO = new CRM_Core_DAO_Address(); while ($dao->fetch()) { + // call the function to update the relationship + if ($createRelationship && isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) { + self::processSharedAddressRelationship($params['master_id'], $dao->contact_id); + } $addressDAO->copyValues($params); $addressDAO->id = $dao->id; $addressDAO->save(); - $addressDAO->free(); } } @@ -1028,7 +1076,7 @@ public static function processSharedAddress($addressId, $params) { * Array[contact_id][contactDetails]. */ public static function mergeSameAddress(&$rows) { - $uniqueAddress = array(); + $uniqueAddress = []; foreach (array_keys($rows) as $rowID) { // load complete address as array key $address = trim($rows[$rowID]['street_address']) @@ -1044,12 +1092,12 @@ public static function mergeSameAddress(&$rows) { } // CRM-15120 - $formatted = array( + $formatted = [ 'first_name' => $rows[$rowID]['first_name'], 'individual_prefix' => $rows[$rowID]['individual_prefix'], - ); + ]; $format = Civi::settings()->get('display_name_format'); - $firstNameWithPrefix = CRM_Utils_Address::format($formatted, $format, FALSE, FALSE, TRUE); + $firstNameWithPrefix = CRM_Utils_Address::format($formatted, $format, FALSE, FALSE); $firstNameWithPrefix = trim($firstNameWithPrefix); // fill uniqueAddress array with last/first name tree @@ -1098,18 +1146,17 @@ public static function mergeSameAddress(&$rows) { /** * Create relationship between contacts who share an address. * - * Note that currently we create relationship only for Individual contacts - * Individual + Household and Individual + Orgnization + * Note that currently we create relationship between + * Individual + Household and Individual + Organization * * @param int $masterAddressId * Master address id. - * @param array $params - * Associated array of submitted values. + * @param int $currentContactId + * Current contact id. */ - public static function processSharedAddressRelationship($masterAddressId, $params) { + public static function processSharedAddressRelationship($masterAddressId, $currentContactId) { // get the contact type of contact being edited / created - $currentContactType = CRM_Contact_BAO_Contact::getContactType($params['contact_id']); - $currentContactId = $params['contact_id']; + $currentContactType = CRM_Contact_BAO_Contact::getContactType($currentContactId); // if current contact is not of type individual return if ($currentContactType != 'Individual') { @@ -1122,11 +1169,10 @@ public static function processSharedAddressRelationship($masterAddressId, $param FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id WHERE ca.id = %1'; - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($masterAddressId, 'Integer'))); + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$masterAddressId, 'Integer']]); $dao->fetch(); - // if current contact is not of type individual return, since we don't create relationship between - // 2 individuals + // master address contact needs to be Household or Organization, otherwise return if ($dao->contact_type == 'Individual') { return; } @@ -1145,12 +1191,12 @@ public static function processSharedAddressRelationship($masterAddressId, $param CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Household Member of'")); } - $relParam = array( + $relParam = [ 'is_active' => TRUE, 'relationship_type_id' => $relTypeId, 'contact_id_a' => $currentContactId, 'contact_id_b' => $sharedContactId, - ); + ]; // If already there is a relationship record of $relParam criteria, avoid creating relationship again or else // it will casue CRM-16588 as the Duplicate Relationship Exception will revert other contact field values on update @@ -1197,10 +1243,10 @@ public static function setSharedAddressDeleteStatus($addressId = NULL, $contactI WHERE ca1.contact_id = %1'; } - $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($entityId, 'Integer'))); + $dao = CRM_Core_DAO::executeQuery($query, [1 => [$entityId, 'Integer']]); - $deleteStatus = array(); - $sharedContactList = array(); + $deleteStatus = []; + $sharedContactList = []; $statusMessage = NULL; $addressCount = 0; while ($dao->fetch()) { @@ -1223,10 +1269,10 @@ public static function setSharedAddressDeleteStatus($addressId = NULL, $contactI CRM_Core_Session::setStatus($statusMessage, '', 'info'); } else { - return array( + return [ 'contactList' => $sharedContactList, 'count' => $addressCount, - ); + ]; } } @@ -1256,8 +1302,8 @@ public static function del($id) { * * @return array|bool */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { - $params = array(); + public static function buildOptions($fieldName, $context = NULL, $props = []) { + $params = []; // Special logic for fields whose options depend on context or properties switch ($fieldName) { // Filter state_province list based on chosen country or site defaults @@ -1266,7 +1312,7 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( case 'state_province': // change $fieldName to DB specific names. $fieldName = 'state_province_id'; - if (empty($props['country_id'])) { + if (empty($props['country_id']) && $context !== 'validate') { $config = CRM_Core_Config::singleton(); if (!empty($config->provinceLimit)) { $props['country_id'] = $config->provinceLimit; @@ -1275,7 +1321,10 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( $props['country_id'] = $config->defaultContactCountry; } } - if (!empty($props['country_id']) && $context !== 'validate') { + if (!empty($props['country_id'])) { + if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['country_id']))) { + throw new CRM_Core_Exception(ts('Province limit or default country setting is incorrect')); + } $params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')'; } break; @@ -1288,6 +1337,9 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( if ($context != 'get' && $context != 'validate') { $config = CRM_Core_Config::singleton(); if (!empty($config->countryLimit) && is_array($config->countryLimit)) { + if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', $config->countryLimit))) { + throw new CRM_Core_Exception(ts('Available Country setting is incorrect')); + } $params['condition'] = 'id IN (' . implode(',', $config->countryLimit) . ')'; } } @@ -1296,6 +1348,9 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( // Filter county list based on chosen state case 'county_id': if (!empty($props['state_province_id'])) { + if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['state_province_id']))) { + throw new CRM_Core_Exception(ts('Can only accept Integers for state_province_id filtering')); + } $params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')'; } break; @@ -1304,9 +1359,29 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( case 'world_region': case 'worldregion': case 'worldregion_id': - return CRM_Core_PseudoConstant::worldRegion(); + return CRM_Core_BAO_Country::buildOptions('region_id', $context, $props); } return CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context); } + /** + * Add data from the configured geocoding provider. + * + * Generally this means latitude & longitude data. + * + * @param array $params + * @return bool + * TRUE if params could be passed to a provider, else FALSE. + */ + public static function addGeocoderData(&$params) { + try { + $provider = CRM_Utils_GeocodeProvider::getConfiguredProvider(); + } + catch (CRM_Core_Exception $e) { + return FALSE; + } + $provider::format($params); + return TRUE; + } + } diff --git a/CRM/Core/BAO/Block.php b/CRM/Core/BAO/Block.php index f12aaa697894..048614246464 100644 --- a/CRM/Core/BAO/Block.php +++ b/CRM/Core/BAO/Block.php @@ -1,9 +1,9 @@ array('email'), - 'phone' => array('phone'), - 'im' => array('name'), - 'openid' => array('openid'), - ); + public static $requiredBlockFields = [ + 'email' => ['email'], + 'phone' => ['phone'], + 'im' => ['name'], + 'openid' => ['openid'], + ]; /** * Given the list of params in the params array, fetch the object @@ -63,7 +64,7 @@ public static function &getValues($blockName, $params) { $BAOString = 'CRM_Core_BAO_' . $blockName; $block = new $BAOString(); - $blocks = array(); + $blocks = []; if (!isset($params['entity_table'])) { $block->contact_id = $params['contact_id']; if (!$block->contact_id) { @@ -108,7 +109,7 @@ public static function retrieveBlock(&$block, $blockName) { $block->find(); $count = 1; - $blocks = array(); + $blocks = []; while ($block->fetch()) { CRM_Core_DAO::storeValues($block, $blocks[$count]); //unset is_primary after first block. Due to some bug in earlier version @@ -178,7 +179,7 @@ public static function blockExists($blockName, &$params) { * */ public static function getBlockIds($blockName, $contactId = NULL, $entityElements = NULL, $updateBlankLocInfo = FALSE) { - $allBlocks = array(); + $allBlocks = []; $name = ucfirst($blockName); if ($blockName == 'im') { @@ -226,15 +227,15 @@ public static function create($blockName, &$params, $entity = NULL, $contactId = $name = ucfirst($blockName); $isPrimary = $isBilling = TRUE; - $entityElements = $blocks = array(); + $entityElements = $blocks = []; $resetPrimaryId = NULL; $primaryId = FALSE; if ($entity) { - $entityElements = array( + $entityElements = [ 'entity_table' => $params['entity_table'], 'entity_id' => $params['entity_id'], - ); + ]; } else { $contactId = $params['contact_id']; @@ -276,7 +277,6 @@ public static function create($blockName, &$params, $entity = NULL, $contactId = $block->is_primary = FALSE; $block->save(); } - $block->free(); } } } @@ -320,16 +320,16 @@ public static function create($blockName, &$params, $entity = NULL, $contactId = // $updateBlankLocInfo will help take appropriate decision. CRM-5969 if (!empty($value['id']) && !$dataExists && $updateBlankLocInfo) { //delete the existing record - self::blockDelete($blockName, array('id' => $value['id'])); + self::blockDelete($blockName, ['id' => $value['id']]); continue; } elseif (!$dataExists) { continue; } - $contactFields = array( + $contactFields = [ 'contact_id' => $contactId, 'location_type_id' => CRM_Utils_Array::value('location_type_id', $value), - ); + ]; $contactFields['is_primary'] = 0; if ($isPrimary && !empty($value['is_primary'])) { @@ -344,8 +344,18 @@ public static function create($blockName, &$params, $entity = NULL, $contactId = } $blockFields = array_merge($value, $contactFields); - $baoString = 'CRM_Core_BAO_' . $name; - $blocks[] = $baoString::add($blockFields); + if ($name === 'Email') { + // @todo ideally all would call the api which is our main tested function, + // and towards that call the create rather than add which is preferred by the + // api. In order to be careful with change only email is swapped over here because it + // is specifically tested in testImportParserWithUpdateWithContactID + // and the primary handling is otherwise bypassed on importing an email update. + $blocks[] = CRM_Core_BAO_Email::create($blockFields); + } + else { + $baoString = 'CRM_Core_BAO_' . $name; + $blocks[] = $baoString::add($blockFields); + } } return $blocks; @@ -422,11 +432,11 @@ public static function handlePrimary(&$params, $class) { // if is_primary = 1 if (!empty($params['is_primary'])) { $sql = "UPDATE $table SET is_primary = 0 WHERE contact_id = %1"; - $sqlParams = array(1 => array($contactId, 'Integer')); + $sqlParams = [1 => [$contactId, 'Integer']]; // we don't want to create unnecessary entries in the log_ tables so exclude the one we are working on if (!empty($params['id'])) { $sql .= " AND id <> %2"; - $sqlParams[2] = array($params['id'], 'Integer'); + $sqlParams[2] = [$params['id'], 'Integer']; } CRM_Core_DAO::executeQuery($sql, $sqlParams); return; diff --git a/CRM/Core/BAO/CMSUser.php b/CRM/Core/BAO/CMSUser.php index 141ba941edd6..36bb8bdb6f0c 100644 --- a/CRM/Core/BAO/CMSUser.php +++ b/CRM/Core/BAO/CMSUser.php @@ -1,9 +1,9 @@ userFramework) == 'Joomla' ? TRUE : FALSE; $isWordPress = $config->userFramework == 'WordPress' ? TRUE : FALSE; - //if CMS is configured for not to allow creating new CMS user, - //don't build the form,Fixed for CRM-4036 - if ($isJoomla) { - $userParams = JComponentHelper::getParams('com_users'); - if (!$userParams->get('allowUserRegistration')) { - return FALSE; - } - } - elseif ($isDrupal && !variable_get('user_register', TRUE)) { - return FALSE; - } - elseif ($isWordPress && !get_option('users_can_register')) { + if (!$config->userSystem->isUserRegistrationPermitted()) { + // Do not build form if CMS is not configured to allow creating users. return FALSE; } @@ -111,8 +101,7 @@ public static function buildForm(&$form, $gid, $emailPresent, $action = CRM_Core } // $cms is true when there is email(primary location) is set in the profile field. - $session = CRM_Core_Session::singleton(); - $userID = $session->get('userID'); + $userID = CRM_Core_Session::singleton()->get('userID'); $showUserRegistration = FALSE; if ($action) { $showUserRegistration = TRUE; @@ -124,9 +113,9 @@ public static function buildForm(&$form, $gid, $emailPresent, $action = CRM_Core if ($isCMSUser && $emailPresent) { if ($showUserRegistration) { if ($isCMSUser != 2) { - $extra = array( + $extra = [ 'onclick' => "return showHideByValue('cms_create_account','','details','block','radio',false );", - ); + ]; $form->addElement('checkbox', 'cms_create_account', ts('Create an account?'), NULL, $extra); $required = FALSE; } @@ -138,12 +127,12 @@ public static function buildForm(&$form, $gid, $emailPresent, $action = CRM_Core $form->assign('isCMS', $required); if (!$userID || $action & CRM_Core_Action::PREVIEW || $action & CRM_Core_Action::PROFILE) { $form->add('text', 'cms_name', ts('Username'), NULL, $required); - if (($isDrupal && !variable_get('user_email_verification', TRUE)) OR ($isJoomla) OR ($isWordPress)) { + if ($config->userSystem->isPasswordUserGenerated()) { $form->add('password', 'cms_pass', ts('Password')); $form->add('password', 'cms_confirm_pass', ts('Confirm Password')); } - $form->addFormRule(array('CRM_Core_BAO_CMSUser', 'formRule'), $form); + $form->addFormRule(['CRM_Core_BAO_CMSUser', 'formRule'], $form); } $showCMS = TRUE; } @@ -178,7 +167,7 @@ public static function formRule($fields, $files, $form) { $isJoomla = ucfirst($config->userFramework) == 'Joomla' ? TRUE : FALSE; $isWordPress = $config->userFramework == 'WordPress' ? TRUE : FALSE; - $errors = array(); + $errors = []; if ($isDrupal || $isJoomla || $isWordPress) { $emailName = NULL; if (!empty($form->_bltID) && array_key_exists("email-{$form->_bltID}", $fields)) { @@ -208,7 +197,7 @@ public static function formRule($fields, $files, $form) { $errors[$emailName] = ts('Please specify a valid email address.'); } - if (($isDrupal && !variable_get('user_email_verification', TRUE)) OR ($isJoomla) OR ($isWordPress)) { + if ($config->userSystem->isPasswordUserGenerated()) { if (empty($fields['cms_pass']) || empty($fields['cms_confirm_pass']) ) { @@ -224,11 +213,11 @@ public static function formRule($fields, $files, $form) { } // now check that the cms db does not have the user name and/or email - if ($isDrupal OR $isJoomla OR $isWordPress) { - $params = array( + if ($isDrupal or $isJoomla or $isWordPress) { + $params = [ 'name' => $fields['cms_name'], 'mail' => $fields[$emailName], - ); + ]; } $config->userSystem->checkUserNameEmailExists($params, $errors, $emailName); diff --git a/CRM/Core/BAO/Cache.php b/CRM/Core/BAO/Cache.php index 31c4355b6bea..e71e24e368c9 100644 --- a/CRM/Core/BAO/Cache.php +++ b/CRM/Core/BAO/Cache.php @@ -1,9 +1,9 @@ $cacheValue) + * When store session/form state, how long should the data be retained? + * + * Default is Two days: 2*24*60*60 + * + * @var int, number of second */ - static $_cache = NULL; + const DEFAULT_SESSION_TTL = 172800; + + /** + * Cache. + * + * Format is ($cacheKey => $cacheValue) + * + * @var array + */ + public static $_cache = NULL; /** * Retrieve an item from the DB cache. @@ -57,24 +70,37 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache { * * @return object * The data if present in cache, else null + * @deprecated */ public static function &getItem($group, $path, $componentID = NULL) { + CRM_Core_Error::deprecatedFunctionWarning( + 'CRM_Core_BAO_Cache::getItem is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' + ); + if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { + $value = $adapter::getItem($group, $path, $componentID); + return $value; + } + if (self::$_cache === NULL) { - self::$_cache = array(); + self::$_cache = []; } $argString = "CRM_CT_{$group}_{$path}_{$componentID}"; if (!array_key_exists($argString, self::$_cache)) { $cache = CRM_Utils_Cache::singleton(); - self::$_cache[$argString] = $cache->get($argString); - if (!self::$_cache[$argString]) { + $cleanKey = self::cleanKey($argString); + self::$_cache[$argString] = $cache->get($cleanKey); + if (self::$_cache[$argString] === NULL) { $table = self::getTableName(); $where = self::whereCache($group, $path, $componentID); $rawData = CRM_Core_DAO::singleValueQuery("SELECT data FROM $table WHERE $where"); - $data = $rawData ? unserialize($rawData) : NULL; + $data = $rawData ? self::decode($rawData) : NULL; self::$_cache[$argString] = $data; - $cache->set($argString, self::$_cache[$argString]); + if ($data !== NULL) { + // Do not cache 'null' as that is most likely a cache miss & we shouldn't then cache it. + $cache->set($cleanKey, self::$_cache[$argString]); + } } } return self::$_cache[$argString]; @@ -90,29 +116,37 @@ public static function &getItem($group, $path, $componentID = NULL) { * * @return object * The data if present in cache, else null + * @deprecated */ public static function &getItems($group, $componentID = NULL) { + CRM_Core_Error::deprecatedFunctionWarning( + 'CRM_Core_BAO_Cache::getItems is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' + ); + if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { + return $adapter::getItems($group, $componentID); + } + if (self::$_cache === NULL) { - self::$_cache = array(); + self::$_cache = []; } $argString = "CRM_CT_CI_{$group}_{$componentID}"; if (!array_key_exists($argString, self::$_cache)) { $cache = CRM_Utils_Cache::singleton(); - self::$_cache[$argString] = $cache->get($argString); + $cleanKey = self::cleanKey($argString); + self::$_cache[$argString] = $cache->get($cleanKey); if (!self::$_cache[$argString]) { $table = self::getTableName(); $where = self::whereCache($group, NULL, $componentID); $dao = CRM_Core_DAO::executeQuery("SELECT path, data FROM $table WHERE $where"); - $result = array(); + $result = []; while ($dao->fetch()) { - $result[$dao->path] = unserialize($dao->data); + $result[$dao->path] = self::decode($dao->data); } - $dao->free(); self::$_cache[$argString] = $result; - $cache->set($argString, self::$_cache[$argString]); + $cache->set($cleanKey, self::$_cache[$argString]); } } @@ -130,10 +164,18 @@ public static function &getItems($group, $componentID = NULL) { * (required) The path under which this item is stored. * @param int $componentID * The optional component ID (so componenets can share the same name space). + * @deprecated */ public static function setItem(&$data, $group, $path, $componentID = NULL) { + CRM_Core_Error::deprecatedFunctionWarning( + 'CRM_Core_BAO_Cache::setItem is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' + ); + if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { + return $adapter::setItem($data, $group, $path, $componentID); + } + if (self::$_cache === NULL) { - self::$_cache = array(); + self::$_cache = []; } // get a lock so that multiple ajax requests on the same page @@ -147,46 +189,45 @@ public static function setItem(&$data, $group, $path, $componentID = NULL) { $table = self::getTableName(); $where = self::whereCache($group, $path, $componentID); $dataExists = CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM $table WHERE {$where}"); - $now = date('Y-m-d H:i:s'); // FIXME - Use SQL NOW() or CRM_Utils_Time? - $dataSerialized = serialize($data); + // FIXME - Use SQL NOW() or CRM_Utils_Time? + $now = date('Y-m-d H:i:s'); + $dataSerialized = self::encode($data); // This table has a wonky index, so we cannot use REPLACE or // "INSERT ... ON DUPE". Instead, use SELECT+(INSERT|UPDATE). if ($dataExists) { $sql = "UPDATE $table SET data = %1, created_date = %2 WHERE {$where}"; - $args = array( - 1 => array($dataSerialized, 'String'), - 2 => array($now, 'String'), - ); + $args = [ + 1 => [$dataSerialized, 'String'], + 2 => [$now, 'String'], + ]; $dao = CRM_Core_DAO::executeQuery($sql, $args, TRUE, NULL, FALSE, FALSE); } else { $insert = CRM_Utils_SQL_Insert::into($table) - ->row(array( + ->row([ 'group_name' => $group, 'path' => $path, 'component_id' => $componentID, 'data' => $dataSerialized, 'created_date' => $now, - )); - $dao = CRM_Core_DAO::executeQuery($insert->toSQL(), array(), TRUE, NULL, FALSE, FALSE); + ]); + $dao = CRM_Core_DAO::executeQuery($insert->toSQL(), [], TRUE, NULL, FALSE, FALSE); } $lock->release(); - $dao->free(); - // cache coherency - refresh or remove dependent caches $argString = "CRM_CT_{$group}_{$path}_{$componentID}"; $cache = CRM_Utils_Cache::singleton(); - $data = unserialize($dataSerialized); + $data = self::decode($dataSerialized); self::$_cache[$argString] = $data; - $cache->set($argString, $data); + $cache->set(self::cleanKey($argString), $data); $argString = "CRM_CT_CI_{$group}_{$componentID}"; unset(self::$_cache[$argString]); - $cache->delete($argString); + $cache->delete(self::cleanKey($argString)); } /** @@ -197,21 +238,38 @@ public static function setItem(&$data, $group, $path, $componentID = NULL) { * @param string $path * Path of the item that needs to be deleted. * @param bool $clearAll clear all caches + * @deprecated */ public static function deleteGroup($group = NULL, $path = NULL, $clearAll = TRUE) { - $table = self::getTableName(); - $where = self::whereCache($group, $path, NULL); - CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where"); + CRM_Core_Error::deprecatedFunctionWarning( + 'CRM_Core_BAO_Cache::deleteGroup is deprecated and will be removed from core soon, use Civi::cache() facade or define cache group using hook_civicrm_container' + ); + if (($adapter = CRM_Utils_Constant::value('CIVICRM_BAO_CACHE_ADAPTER')) !== NULL) { + return $adapter::deleteGroup($group, $path); + } + else { + $table = self::getTableName(); + $where = self::whereCache($group, $path, NULL); + CRM_Core_DAO::executeQuery("DELETE FROM $table WHERE $where"); + } if ($clearAll) { - // also reset ACL Cache - CRM_ACL_BAO_Cache::resetCache(); - - // also reset memory cache if any - CRM_Utils_System::flushCache(); + self::resetCaches(); } } + /** + * Cleanup ACL and System Level caches + */ + public static function resetCaches() { + // also reset ACL Cache + // @todo why is this called when CRM_Utils_System::flushCache() does it as well. + CRM_ACL_BAO_Cache::resetCache(); + + // also reset memory cache if any + CRM_Utils_System::flushCache(); + } + /** * The next two functions are internal functions used to store and retrieve session from * the database cache. This keeps the session to a limited size and allows us to @@ -237,7 +295,8 @@ public static function storeSessionToCache($names, $resetSession = TRUE) { if (!empty($_SESSION[$sessionName[0]][$sessionName[1]])) { $value = $_SESSION[$sessionName[0]][$sessionName[1]]; } - self::setItem($value, 'CiviCRM Session', "{$sessionName[0]}_{$sessionName[1]}"); + $key = "{$sessionName[0]}_{$sessionName[1]}"; + Civi::cache('session')->set($key, $value, self::pickSessionTtl($key)); if ($resetSession) { $_SESSION[$sessionName[0]][$sessionName[1]] = NULL; unset($_SESSION[$sessionName[0]][$sessionName[1]]); @@ -248,7 +307,7 @@ public static function storeSessionToCache($names, $resetSession = TRUE) { if (!empty($_SESSION[$sessionName])) { $value = $_SESSION[$sessionName]; } - self::setItem($value, 'CiviCRM Session', $sessionName); + Civi::cache('session')->set($sessionName, $value, self::pickSessionTtl($sessionName)); if ($resetSession) { $_SESSION[$sessionName] = NULL; unset($_SESSION[$sessionName]); @@ -275,17 +334,13 @@ public static function storeSessionToCache($names, $resetSession = TRUE) { public static function restoreSessionFromCache($names) { foreach ($names as $key => $sessionName) { if (is_array($sessionName)) { - $value = self::getItem('CiviCRM Session', - "{$sessionName[0]}_{$sessionName[1]}" - ); + $value = Civi::cache('session')->get("{$sessionName[0]}_{$sessionName[1]}"); if ($value) { $_SESSION[$sessionName[0]][$sessionName[1]] = $value; } } else { - $value = self::getItem('CiviCRM Session', - $sessionName - ); + $value = Civi::cache('session')->get($sessionName); if ($value) { $_SESSION[$sessionName] = $value; } @@ -293,6 +348,32 @@ public static function restoreSessionFromCache($names) { } } + /** + * Determine how long session-state should be retained. + * + * @param string $sessionKey + * Ex: '_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654_container' + * Ex: 'CiviCRM_CRM_Admin_Form_Preferences_Display_f1a5f232e3d850a29a7a4d4079d7c37b_4654' + * @return int + * Number of seconds. + */ + protected static function pickSessionTtl($sessionKey) { + $secureSessionTimeoutMinutes = (int) Civi::settings()->get('secure_cache_timeout_minutes'); + if ($secureSessionTimeoutMinutes) { + $transactionPages = [ + 'CRM_Contribute_Controller_Contribution', + 'CRM_Event_Controller_Registration', + ]; + foreach ($transactionPages as $transactionPage) { + if (strpos($sessionKey, $transactionPage) !== FALSE) { + return $secureSessionTimeoutMinutes * 60; + } + } + } + + return self::DEFAULT_SESSION_TTL; + } + /** * Do periodic cleanup of the CiviCRM session table. * @@ -303,34 +384,9 @@ public static function restoreSessionFromCache($names) { * @param bool $session * @param bool $table * @param bool $prevNext + * @param bool $expired */ - public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE) { - // first delete all sessions more than 20 minutes old which are related to any potential transaction - $timeIntervalMins = (int) Civi::settings()->get('secure_cache_timeout_minutes'); - if ($timeIntervalMins && $session) { - $transactionPages = array( - 'CRM_Contribute_Controller_Contribution', - 'CRM_Event_Controller_Registration', - ); - - $params = array( - 1 => array( - date('Y-m-d H:i:s', time() - $timeIntervalMins * 60), - 'String', - ), - ); - foreach ($transactionPages as $trPage) { - $params[] = array("%${trPage}%", 'String'); - $where[] = 'path LIKE %' . count($params); - } - - $sql = " -DELETE FROM civicrm_cache -WHERE group_name = 'CiviCRM Session' -AND created_date <= %1 -AND (" . implode(' OR ', $where) . ")"; - CRM_Core_DAO::executeQuery($sql, $params); - } + public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FALSE, $expired = FALSE) { // clean up the session cache every $cacheCleanUpNumber probabilistically $cleanUpNumber = 757; @@ -338,10 +394,10 @@ public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FAL $timeIntervalDays = 2; if (mt_rand(1, 100000) % $cleanUpNumber == 0) { - $session = $table = $prevNext = TRUE; + $expired = $session = $table = $prevNext = TRUE; } - if (!$session && !$table && !$prevNext) { + if (!$session && !$table && !$prevNext && !$expired) { return; } @@ -355,13 +411,43 @@ public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FAL } if ($session) { + // Session caches are just regular caches, so they expire naturally per TTL. + $expired = TRUE; + } - $sql = " -DELETE FROM civicrm_cache -WHERE group_name = 'CiviCRM Session' -AND created_date < date_sub( NOW( ), INTERVAL $timeIntervalDays DAY ) -"; - CRM_Core_DAO::executeQuery($sql); + if ($expired) { + $sql = "DELETE FROM civicrm_cache WHERE expired_date < %1"; + $params = [ + 1 => [date(CRM_Utils_Cache_SqlGroup::TS_FMT, CRM_Utils_Time::getTimeRaw()), 'String'], + ]; + CRM_Core_DAO::executeQuery($sql, $params); + } + } + + /** + * (Quasi-private) Encode an object/array/string/int as a string. + * + * @param $mixed + * @return string + */ + public static function encode($mixed) { + return base64_encode(serialize($mixed)); + } + + /** + * (Quasi-private) Decode an object/array/string/int from a string. + * + * @param $string + * @return mixed + */ + public static function decode($string) { + // Upgrade support -- old records (serialize) always have this punctuation, + // and new records (base64) never do. + if (strpos($string, ':') !== FALSE || strpos($string, ';') !== FALSE) { + return unserialize($string); + } + else { + return unserialize(base64_decode($string)); } } @@ -372,14 +458,14 @@ public static function cleanup($session = FALSE, $table = FALSE, $prevNext = FAL * full access to DAO services. * * @param string $group - * @param string|NULL $path + * @param string|null $path * Filter by path. If NULL, then return any paths. - * @param int|NULL $componentID + * @param int|null $componentID * Filter by component. If NULL, then look for explicitly NULL records. * @return string */ protected static function whereCache($group, $path, $componentID) { - $clauses = array(); + $clauses = []; $clauses[] = ('group_name = "' . CRM_Core_DAO::escapeString($group) . '"'); if ($path) { $clauses[] = ('path = "' . CRM_Core_DAO::escapeString($path) . '"'); @@ -390,4 +476,22 @@ protected static function whereCache($group, $path, $componentID) { return $clauses ? implode(' AND ', $clauses) : '(1)'; } + /** + * Normalize a cache key. + * + * This bridges an impedance mismatch between our traditional caching + * and PSR-16 -- PSR-16 accepts a narrower range of cache keys. + * + * @param string $key + * Ex: 'ab/cd:ef' + * @return string + * Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'. + * A similar key, but suitable for use with PSR-16-compliant cache providers. + * @deprecated + * @see CRM_Utils_Cache::cleanKey() + */ + public static function cleanKey($key) { + return CRM_Utils_Cache::cleanKey($key); + } + } diff --git a/CRM/Core/BAO/Cache/Psr16.php b/CRM/Core/BAO/Cache/Psr16.php new file mode 100644 index 000000000000..6e7e4e6a03e3 --- /dev/null +++ b/CRM/Core/BAO/Cache/Psr16.php @@ -0,0 +1,231 @@ +warning('Unrecognized BAO cache group ({group}). This should work generally, but data may not be flushed in some edge-cases. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + ]); + } + + $cache = CRM_Utils_Cache::create([ + 'name' => "bao_$group", + 'type' => ['*memory*', 'SqlGroup', 'ArrayCache'], + // We're replacing CRM_Core_BAO_Cache, which traditionally used a front-cache + // that was not aware of TTLs. So it seems more consistent/performant to + // use 'fast' here. + 'withArray' => 'fast', + ]); + Civi::$statics[__CLASS__][$group] = $cache; + } + return Civi::$statics[__CLASS__][$group]; + } + + /** + * Retrieve an item from the DB cache. + * + * @param string $group + * (required) The group name of the item. + * @param string $path + * (required) The path under which this item is stored. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + * + * @return object + * The data if present in cache, else null + */ + public static function getItem($group, $path, $componentID = NULL) { + // TODO: Generate a general deprecation notice. + if ($componentID) { + Civi::log() + ->warning('getItem({group},{path},...) uses unsupported componentID. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + 'path' => $path, + ]); + } + return self::getGroup($group)->get(CRM_Utils_Cache::cleanKey($path)); + } + + /** + * Retrieve all items in a group. + * + * @param string $group + * (required) The group name of the item. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + * + * @throws CRM_Core_Exception + */ + public static function &getItems($group, $componentID = NULL) { + // Based on grepping universe, this function is not currently used. + // Moreover, it's hard to implement in PSR-16. (We'd have to extend the + // interface.) Let's wait and see if anyone actually needs this... + throw new \CRM_Core_Exception('Not implemented: CRM_Core_BAO_Cache_Psr16::getItems'); + } + + /** + * Store an item in the DB cache. + * + * @param object $data + * (required) A reference to the data that will be serialized and stored. + * @param string $group + * (required) The group name of the item. + * @param string $path + * (required) The path under which this item is stored. + * @param int $componentID + * The optional component ID (so componenets can share the same name space). + */ + public static function setItem(&$data, $group, $path, $componentID = NULL) { + // TODO: Generate a general deprecation notice. + + if ($componentID) { + Civi::log() + ->warning('setItem({group},{path},...) uses unsupported componentID. Consider migrating explicitly to PSR-16.', [ + 'group' => $group, + 'path' => $path, + ]); + } + self::getGroup($group) + ->set(CRM_Utils_Cache::cleanKey($path), $data, self::TTL); + } + + /** + * Delete all the cache elements that belong to a group OR delete the entire cache if group is not specified. + * + * @param string $group + * The group name of the entries to be deleted. + * @param string $path + * Path of the item that needs to be deleted. + */ + public static function deleteGroup($group = NULL, $path = NULL) { + // FIXME: Generate a general deprecation notice. + + if ($path) { + self::getGroup($group)->delete(CRM_Utils_Cache::cleanKey($path)); + } + else { + self::getGroup($group)->clear(); + } + } + + /** + * Cleanup any caches that we've mapped. + * + * Traditional SQL-backed caches are cleared as a matter of course during a + * system flush (by way of "TRUNCATE TABLE civicrm_cache"). This provides + * a spot where the adapter can + */ + public static function clearDBCache() { + foreach (self::getLegacyGroups() as $groupName) { + $group = self::getGroup($groupName); + $group->clear(); + } + } + + /** + * Get a list of known cache-groups + * + * @return array + */ + public static function getLegacyGroups() { + $groups = [ + // Universe + + // biz.jmaconsulting.lineitemedit + 'lineitem-editor', + + // civihr/uk.co.compucorp.civicrm.hrcore + 'HRCore_Info', + + ]; + // Handle Legacy Multisite caching group. + $extensions = CRM_Extension_System::singleton()->getManager(); + $multisiteExtensionStatus = $extensions->getStatus('org.civicrm.multisite'); + if ($multisiteExtensionStatus == $extensions::STATUS_INSTALLED) { + $extension_version = civicrm_api3('Extension', 'get', ['key' => 'org.civicrm.multisite'])['values'][0]['version']; + if (version_compare($extension_version, '2.7', '<')) { + Civi::log()->warning( + 'CRM_Core_BAO_Cache_PSR is deprecated for multisite extension, you should upgrade to the latest version to avoid this warning, this code will be removed at the end of 2019', + ['civi.tag' => 'deprecated'] + ); + $groups[] = 'descendant groups for an org'; + } + } + $entitySettingExtensionStatus = $extensions->getStatus('nz.co.fuzion.entitysetting'); + if ($multisiteExtensionStatus == $extensions::STATUS_INSTALLED) { + $extension_version = civicrm_api3('Extension', 'get', ['key' => 'nz.co.fuzion.entitysetting'])['values'][0]['version']; + if (version_compare($extension_version, '1.3', '<')) { + Civi::log()->warning( + 'CRM_Core_BAO_Cache_PSR is deprecated for entity setting extension, you should upgrade to the latest version to avoid this warning, this code will be removed at the end of 2019', + ['civi.tag' => 'deprecated'] + ); + $groups[] = 'CiviCRM setting Spec'; + } + } + $atomFeedsSettingExtensionStatus = $extensions->getStatus('be.chiro.civi.atomfeeds'); + if ($atomFeedsSettingExtensionStatus == $extensions::STATUS_INSTALLED) { + $extension_version = civicrm_api3('Extension', 'get', ['key' => 'be.chiro.civi.atomfeeds'])['values'][0]['version']; + if (version_compare($extension_version, '0.1-alpha2', '<')) { + Civi::log()->warning( + 'CRM_Core_BAO_Cache_PSR is deprecated for Atomfeeds extension, you should upgrade to the latest version to avoid this warning, this code will be removed at the end of 2019', + ['civi.tag' => 'deprecated'] + ); + $groups[] = 'dashboard'; + } + } + return $groups; + } + +} diff --git a/CRM/Core/BAO/ConfigSetting.php b/CRM/Core/BAO/ConfigSetting.php index 0e0d838d66de..8919a6d79095 100644 --- a/CRM/Core/BAO/ConfigSetting.php +++ b/CRM/Core/BAO/ConfigSetting.php @@ -1,9 +1,9 @@ selectAdd('config_backend'); } else { @@ -108,7 +108,7 @@ public static function retrieve(&$defaults) { if ($domain->config_backend) { $defaults = unserialize($domain->config_backend); if ($defaults === FALSE || !is_array($defaults)) { - $defaults = array(); + $defaults = []; return FALSE; } @@ -141,20 +141,22 @@ public static function applyLocale($settings, $activatedLocales) { $session = CRM_Core_Session::singleton(); - // on multi-lang sites based on request and civicrm_uf_match - if ($multiLang) { - $languageLimit = array(); - if (is_array($settings->get('languageLimit'))) { - $languageLimit = $settings->get('languageLimit'); - } + $permittedLanguages = CRM_Core_I18n::uiLanguages(TRUE); + // The locale to be used can come from various places: + // - the request (url) + // - the session + // - civicrm_uf_match + // - inherited from the CMS + // Only look at this if there is actually a choice of permitted languages + if (count($permittedLanguages) >= 2) { $requestLocale = CRM_Utils_Request::retrieve('lcMessages', 'String'); - if (in_array($requestLocale, array_keys($languageLimit))) { + if (in_array($requestLocale, $permittedLanguages)) { $chosenLocale = $requestLocale; //CRM-8559, cache navigation do not respect locale if it is changed, so reseting cache. // Ed: This doesn't sound good. - CRM_Core_BAO_Cache::deleteGroup('navigation'); + // Civi::cache('navigation')->flush(); } else { $requestLocale = NULL; @@ -162,7 +164,7 @@ public static function applyLocale($settings, $activatedLocales) { if (!$requestLocale) { $sessionLocale = $session->get('lcMessages'); - if (in_array($sessionLocale, array_keys($languageLimit))) { + if (in_array($sessionLocale, $permittedLanguages)) { $chosenLocale = $sessionLocale; } else { @@ -184,7 +186,7 @@ public static function applyLocale($settings, $activatedLocales) { $ufm = new CRM_Core_DAO_UFMatch(); $ufm->contact_id = $session->get('userID'); if ($ufm->find(TRUE) && - in_array($ufm->language, array_keys($languageLimit)) + in_array($ufm->language, $permittedLanguages) ) { $chosenLocale = $ufm->language; } @@ -196,7 +198,8 @@ public static function applyLocale($settings, $activatedLocales) { // try to inherit the language from the hosting CMS if ($settings->get('inheritLocale')) { // FIXME: On multilanguage installs, CRM_Utils_System::getUFLocale() in many cases returns nothing if $dbLocale is not set - $dbLocale = $multiLang ? ("_" . $settings->get('lcMessages')) : ''; + $lcMessages = $settings->get('lcMessages'); + $dbLocale = $multiLang && $lcMessages ? "_{$lcMessages}" : ''; $chosenLocale = CRM_Utils_System::getUFLocale(); if ($activatedLocales and !in_array($chosenLocale, explode(CRM_Core_DAO::VALUE_SEPARATOR, $activatedLocales))) { $chosenLocale = NULL; @@ -209,7 +212,7 @@ public static function applyLocale($settings, $activatedLocales) { } // set suffix for table names - use views if more than one language - $dbLocale = $multiLang ? "_{$chosenLocale}" : ''; + $dbLocale = $multiLang && $chosenLocale ? "_{$chosenLocale}" : ''; // FIXME: an ugly hack to fix CRM-4041 global $tsLocale; @@ -228,7 +231,7 @@ public static function applyLocale($settings, $activatedLocales) { * @return string * @throws Exception */ - public static function doSiteMove($defaultValues = array()) { + public static function doSiteMove($defaultValues = []) { $moveStatus = ts('Beginning site move process...') . '
    '; $settings = Civi::settings(); @@ -237,15 +240,15 @@ public static function doSiteMove($defaultValues = array()) { if ($value && $value != $settings->getDefault($key)) { if ($settings->getMandatory($key) === NULL) { $settings->revert($key); - $moveStatus .= ts("WARNING: The setting (%1) has been reverted.", array( + $moveStatus .= ts("WARNING: The setting (%1) has been reverted.", [ 1 => $key, - )); + ]); $moveStatus .= '
    '; } else { - $moveStatus .= ts("WARNING: The setting (%1) is overridden and could not be reverted.", array( + $moveStatus .= ts("WARNING: The setting (%1) is overridden and could not be reverted.", [ 1 => $key, - )); + ]); $moveStatus .= '
    '; } } @@ -259,6 +262,7 @@ public static function doSiteMove($defaultValues = array()) { // clear all caches CRM_Core_Config::clearDBCache(); + Civi::cache('session')->clear(); $moveStatus .= ts('Database cache tables cleared.') . '
    '; $resetSessionTable = CRM_Utils_Request::retrieve('resetSessionTable', @@ -332,7 +336,7 @@ public static function disableComponent($componentName) { // get enabled-components from DB and add to the list $enabledComponents = Civi::settings()->get('enable_components'); - $enabledComponents = array_diff($enabledComponents, array($componentName)); + $enabledComponents = array_diff($enabledComponents, [$componentName]); self::setEnabledComponents($enabledComponents); @@ -356,7 +360,7 @@ public static function setEnabledComponents($enabledComponents) { * @return array */ public static function skipVars() { - return array( + return [ 'dsn', 'templateCompileDir', 'userFrameworkDSN', @@ -382,7 +386,7 @@ public static function skipVars() { 'autocompleteContactReference', 'checksumTimeout', 'checksum_timeout', - ); + ]; } /** @@ -406,26 +410,26 @@ public static function filterSkipVars($params) { * @return array */ private static function getUrlSettings() { - return array( + return [ 'userFrameworkResourceURL', 'imageUploadURL', 'customCSSURL', 'extensionsURL', - ); + ]; } /** * @return array */ private static function getPathSettings() { - return array( + return [ 'uploadDir', 'imageUploadDir', 'customFileUploadDir', 'customTemplateDir', 'customPHPPathDir', 'extensionsDir', - ); + ]; } } diff --git a/CRM/Core/BAO/Country.php b/CRM/Core/BAO/Country.php index 23b3d64eae46..f6059a6debce 100644 --- a/CRM/Core/BAO/Country.php +++ b/CRM/Core/BAO/Country.php @@ -1,9 +1,9 @@ get('provinceLimit'); - $country = array(); + $country = []; if (is_array($provinceLimit)) { foreach ($provinceLimit as $val) { // CRM-12007 @@ -74,7 +74,7 @@ public static function provinceLimit() { public static function countryLimit() { if (!isset(Civi::$statics[__CLASS__]['countryLimit'])) { $countryIsoCodes = CRM_Core_PseudoConstant::countryIsoCode(); - $country = array(); + $country = []; $countryLimit = Civi::settings()->get('countryLimit'); if (is_array($countryLimit)) { foreach ($countryLimit as $val) { @@ -139,10 +139,10 @@ public static function defaultCurrencySymbol($defaultCurrency = NULL) { if (!$cachedSymbol || $defaultCurrency) { $currency = $defaultCurrency ? $defaultCurrency : Civi::settings()->get('defaultCurrency'); if ($currency) { - $currencySymbols = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', array( + $currencySymbols = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'currency', [ 'labelColumn' => 'symbol', 'orderColumn' => TRUE, - )); + ]); $cachedSymbol = CRM_Utils_Array::value($currency, $currencySymbols, ''); } else { @@ -160,8 +160,7 @@ public static function defaultCurrencySymbol($defaultCurrency = NULL) { * @return string */ public static function getDefaultCurrencySymbol($k = NULL) { - $config = CRM_Core_Config::singleton(); - return $config->defaultCurrencySymbol(Civi::settings()->get('defaultCurrency')); + return CRM_Core_BAO_Country::defaultCurrencySymbol(\Civi::settings()->get('defaultCurrency')); } } diff --git a/CRM/Core/BAO/CustomField.php b/CRM/Core/BAO/CustomField.php index 3e74216e11b9..e5c013ddf7fe 100644 --- a/CRM/Core/BAO/CustomField.php +++ b/CRM/Core/BAO/CustomField.php @@ -1,9 +1,9 @@ CRM_Utils_Type + */ + public static function dataToType() { + return [ + 'String' => CRM_Utils_Type::T_STRING, + 'Int' => CRM_Utils_Type::T_INT, + 'Money' => CRM_Utils_Type::T_MONEY, + 'Memo' => CRM_Utils_Type::T_LONGTEXT, + 'Float' => CRM_Utils_Type::T_FLOAT, + 'Date' => CRM_Utils_Type::T_DATE, + 'DateTime' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, + 'Boolean' => CRM_Utils_Type::T_BOOLEAN, + 'StateProvince' => CRM_Utils_Type::T_INT, + 'File' => CRM_Utils_Type::T_STRING, + 'Link' => CRM_Utils_Type::T_STRING, + 'ContactReference' => CRM_Utils_Type::T_INT, + 'Country' => CRM_Utils_Type::T_INT, + ]; + } + /** * Get data to html array. * @@ -99,7 +123,6 @@ public static function dataToHtml() { 'Radio' => 'Radio', 'CheckBox' => 'CheckBox', 'Multi-Select' => 'Multi-Select', - 'AdvMulti-Select' => 'AdvMulti-Select', 'Autocomplete-Select' => 'Autocomplete-Select', ), array('Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'), @@ -128,198 +151,60 @@ public static function dataToHtml() { * * @return CRM_Core_DAO_CustomField */ - public static function create(&$params) { - $origParams = array_merge(array(), $params); - - $op = empty($params['id']) ? 'create' : 'edit'; - - CRM_Utils_Hook::pre($op, 'CustomField', CRM_Utils_Array::value('id', $params), $params); - - if ($op == 'create') { - if (!isset($params['column_name'])) { - // if add mode & column_name not present, calculate it. - $params['column_name'] = strtolower(CRM_Utils_String::munge($params['label'], '_', 32)); - } - if (!isset($params['name'])) { - $params['name'] = CRM_Utils_String::munge($params['label'], '_', 64); - } - } - else { - $params['column_name'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', - $params['id'], - 'column_name' - ); - } - $columnName = $params['column_name']; - - $indexExist = FALSE; - //as during create if field is_searchable we had created index. - if (!empty($params['id'])) { - $indexExist = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $params['id'], 'is_searchable'); - } - - switch (CRM_Utils_Array::value('html_type', $params)) { - case 'Select Date': - if (empty($params['date_format'])) { - $config = CRM_Core_Config::singleton(); - $params['date_format'] = $config->dateInputFormat; - } - break; - - case 'CheckBox': - case 'AdvMulti-Select': - case 'Multi-Select': - if (isset($params['default_checkbox_option'])) { - $tempArray = array_keys($params['default_checkbox_option']); - $defaultArray = array(); - foreach ($tempArray as $k => $v) { - if ($params['option_value'][$v]) { - $defaultArray[] = $params['option_value'][$v]; - } - } - - if (!empty($defaultArray)) { - // also add the separator before and after the value per new convention (CRM-1604) - $params['default_value'] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $defaultArray) . CRM_Core_DAO::VALUE_SEPARATOR; - } - } - else { - if (!empty($params['default_option']) && isset($params['option_value'][$params['default_option']]) - ) { - $params['default_value'] = $params['option_value'][$params['default_option']]; - } - } - break; - } - - $transaction = new CRM_Core_Transaction(); - // create any option group & values if required - if ($params['html_type'] != 'Text' && - in_array($params['data_type'], array( - 'String', - 'Int', - 'Float', - 'Money', - )) - ) { + public static function create($params) { + $customField = self::createCustomFieldRecord($params); + $op = empty($params['id']) ? 'add' : 'modify'; + self::createField($customField, $op); - $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', - $params['custom_group_id'], - 'table_name' - ); + CRM_Utils_Hook::post(($op === 'add' ? 'create' : 'edit'), 'CustomField', $customField->id, $customField); - //CRM-16659: if option_value then create an option group for this custom field. - if ($params['option_type'] == 1 && (empty($params['option_group_id']) || !empty($params['option_value']))) { - // first create an option group for this custom group - $optionGroup = new CRM_Core_DAO_OptionGroup(); - $optionGroup->name = "{$columnName}_" . date('YmdHis'); - $optionGroup->title = $params['label']; - $optionGroup->is_active = 1; - $optionGroup->data_type = $params['data_type']; - $optionGroup->save(); - $params['option_group_id'] = $optionGroup->id; - if (!empty($params['option_value']) && is_array($params['option_value'])) { - foreach ($params['option_value'] as $k => $v) { - if (strlen(trim($v))) { - $optionValue = new CRM_Core_DAO_OptionValue(); - $optionValue->option_group_id = $optionGroup->id; - $optionValue->label = $params['option_label'][$k]; - $optionValue->name = CRM_Utils_String::titleToVar($params['option_label'][$k]); - switch ($params['data_type']) { - case 'Money': - $optionValue->value = CRM_Utils_Rule::cleanMoney($v); - break; - - case 'Int': - $optionValue->value = intval($v); - break; - - case 'Float': - $optionValue->value = floatval($v); - break; - - default: - $optionValue->value = trim($v); - } + CRM_Utils_System::flushCache(); - $optionValue->weight = $params['option_weight'][$k]; - $optionValue->is_active = CRM_Utils_Array::value($k, $params['option_status'], FALSE); - $optionValue->save(); - } - } - } - } - } + return $customField; + } - // check for orphan option groups - if (!empty($params['option_group_id'])) { - if (!empty($params['id'])) { - self::fixOptionGroups($params['id'], $params['option_group_id']); + /** + * Create/ update several fields at once in a mysql efficient way. + * + * https://lab.civicrm.org/dev/core/issues/1093 + * + * The intention is that apiv4 would expose any BAO with bulkSave as a new action. + * + * @param array $bulkParams + * Array of arrays as would be passed into create + * @param array $defaults + * Default parameters to be be merged into each of the params. + */ + public static function bulkSave($bulkParams, $defaults = []) { + $sql = $tables = $customFields = []; + foreach ($bulkParams as $index => $fieldParams) { + $params = array_merge($defaults, $fieldParams); + $customField = self::createCustomFieldRecord($params); + $fieldSQL = self::getAlterFieldSQL($customField, empty($params['id']) ? 'add' : 'modify'); + if (!isset($params['custom_group_id'])) { + $params['custom_group_id'] = civicrm_api3('CustomField', 'getvalue', ['id' => $customField->id, 'return' => 'custom_group_id']); } - - // if we do not have a default value - // retrieve it from one of the other custom fields which use this option group - if (empty($params['default_value'])) { - //don't insert only value separator as default value, CRM-4579 - $defaultValue = self::getOptionGroupDefault($params['option_group_id'], - $params['html_type'] - ); - - if (!CRM_Utils_System::isNull(explode(CRM_Core_DAO::VALUE_SEPARATOR, - $defaultValue - )) - ) { - $params['default_value'] = $defaultValue; - } + if (!isset($params['table_name'])) { + if (!isset($tables[$params['custom_group_id']])) { + $tables[$params['custom_group_id']] = civicrm_api3('CustomGroup', 'getvalue', [ + 'id' => $params['custom_group_id'], + 'return' => 'table_name', + ]); + } + $params['table_name'] = $tables[$params['custom_group_id']]; } + $sql[$params['table_name']][] = $fieldSQL; + $customFields[$index] = $customField; } - - // since we need to save option group id :) - if (!isset($params['attributes']) && strtolower($params['html_type']) == 'textarea') { - $params['attributes'] = 'rows=4, cols=60'; - } - - $customField = new CRM_Core_DAO_CustomField(); - $customField->copyValues($params); - $customField->is_required = CRM_Utils_Array::value('is_required', $params, FALSE); - $customField->is_searchable = CRM_Utils_Array::value('is_searchable', $params, FALSE); - $customField->in_selector = CRM_Utils_Array::value('in_selector', $params, FALSE); - $customField->is_search_range = CRM_Utils_Array::value('is_search_range', $params, FALSE); - //CRM-15792 - Custom field gets disabled if is_active not set - $customField->is_active = CRM_Utils_Array::value('is_active', $params, TRUE); - $customField->is_view = CRM_Utils_Array::value('is_view', $params, FALSE); - $customField->save(); - - // make sure all values are present in the object for further processing - $customField->find(TRUE); - - $triggerRebuild = CRM_Utils_Array::value('triggerRebuild', $params, TRUE); - //create/drop the index when we toggle the is_searchable flag - if ($op == 'edit') { - self::createField($customField, 'modify', $indexExist, $triggerRebuild); - } - else { - if (!isset($origParams['column_name'])) { - $columnName .= "_{$customField->id}"; - $params['column_name'] = $columnName; - } - $customField->column_name = $columnName; - $customField->save(); - // make sure all values are present in the object - $customField->find(TRUE); - - $indexExist = FALSE; - self::createField($customField, 'add', $indexExist, $triggerRebuild); + foreach ($sql as $tableName => $statements) { + // CRM-7007: do not i18n-rewrite this query + CRM_Core_DAO::executeQuery("ALTER TABLE $tableName " . implode(', ', $statements), [], TRUE, NULL, FALSE, FALSE); + Civi::service('sql_triggers')->rebuild($params['table_name'], TRUE); } - - // complete transaction - $transaction->commit(); - - CRM_Utils_Hook::post($op, 'CustomField', $customField->id, $customField); - CRM_Utils_System::flushCache(); - - return $customField; + foreach ($customFields as $index => $customField) { + CRM_Utils_Hook::post(empty($bulkParams[$index]['id']) ? 'create' : 'edit', 'CustomField', $customField->id, $customField); + } } /** @@ -344,8 +229,8 @@ public static function retrieve(&$params, &$defaults) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { @@ -383,6 +268,9 @@ public function getOptions($context = NULL) { $this->find(TRUE); } + // This will hold the list of options in format key => label + $options = []; + if (!empty($this->option_group_id)) { $options = CRM_Core_OptionGroup::valuesByID( $this->option_group_id, @@ -394,17 +282,14 @@ public function getOptions($context = NULL) { ); } elseif ($this->data_type === 'StateProvince') { - $options = CRM_Core_Pseudoconstant::stateProvince(); + $options = CRM_Core_PseudoConstant::stateProvince(); } elseif ($this->data_type === 'Country') { - $options = $context == 'validate' ? CRM_Core_Pseudoconstant::countryIsoCode() : CRM_Core_Pseudoconstant::country(); + $options = $context == 'validate' ? CRM_Core_PseudoConstant::countryIsoCode() : CRM_Core_PseudoConstant::country(); } elseif ($this->data_type === 'Boolean') { $options = $context == 'validate' ? array(0, 1) : CRM_Core_SelectValues::boolean(); } - else { - return FALSE; - } CRM_Utils_Hook::customFieldOptions($this->id, $options, FALSE); CRM_Utils_Hook::fieldOptions($this->getEntity(), "custom_{$this->id}", $options, array('context' => $context)); return $options; @@ -414,7 +299,7 @@ public function getOptions($context = NULL) { * Store and return an array of all active custom fields. * * @param string $customDataType - * Type of Custom Data; empty is a synonym for "all contact data types". + * Type of Custom Data; 'ANY' is a synonym for "all contact data types". * @param bool $showAll * If true returns all fields (includes disabled fields). * @param bool $inline @@ -446,6 +331,11 @@ public static function &getFields( if (empty($customDataType)) { $customDataType = array('Contact', 'Individual', 'Organization', 'Household'); } + if ($customDataType === 'ANY') { + // NULL should have been respected but the line above broke that. + // boo for us not having enough unit tests back them. + $customDataType = NULL; + } if ($customDataType && !is_array($customDataType)) { if (in_array($customDataType, CRM_Contact_BAO_ContactType::subTypes())) { @@ -479,6 +369,7 @@ public static function &getFields( $cacheKey .= $onlyParent ? '_1_' : '_0_'; $cacheKey .= $onlySubType ? '_1_' : '_0_'; $cacheKey .= $checkPermission ? '_1_' : '_0_'; + $cacheKey .= '_' . CRM_Core_Config::domainID() . '_'; $cgTable = CRM_Core_DAO_CustomGroup::getTableName(); @@ -507,7 +398,7 @@ public static function &getFields( } // check if we can retrieve from database cache - $fields = CRM_Core_BAO_Cache::getItem('contact fields', "custom importableFields $cacheKey"); + $fields = Civi::Cache('fields')->get("custom importableFields $cacheKey"); if ($fields === NULL) { $cfTable = self::getTableName(); @@ -580,7 +471,7 @@ public static function &getFields( if (!empty($customDataSubType)) { $subtypeClause = array(); foreach ($customDataSubType as $subtype) { - $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR; + $subtype = CRM_Core_DAO::VALUE_SEPARATOR . CRM_Utils_Type::escape($subtype, 'String') . CRM_Core_DAO::VALUE_SEPARATOR; $subtypeClause[] = "$cgTable.extends_entity_column_value LIKE '%{$subtype}%'"; } if (!$onlySubType) { @@ -611,9 +502,18 @@ public static function &getFields( $fields = array(); while (($dao->fetch()) != NULL) { + $regexp = preg_replace('/[.,;:!?]/', '', NULL); + $fields[$dao->id]['id'] = $dao->id; $fields[$dao->id]['label'] = $dao->label; + // This seems broken, but not in a new way. + $fields[$dao->id]['headerPattern'] = '/' . preg_quote($regexp, '/') . '/'; + // To support the consolidation of various functions & their expectations. + $fields[$dao->id]['title'] = $dao->label; + $fields[$dao->id]['custom_field_id'] = $dao->id; $fields[$dao->id]['groupTitle'] = $dao->title; $fields[$dao->id]['data_type'] = $dao->data_type; + $fields[$dao->id]['name'] = 'custom_' . $dao->id; + $fields[$dao->id]['type'] = CRM_Utils_Array::value($dao->data_type, self::dataToType()); $fields[$dao->id]['html_type'] = $dao->html_type; $fields[$dao->id]['default_value'] = $dao->default_value; $fields[$dao->id]['text_length'] = $dao->text_length; @@ -631,13 +531,19 @@ public static function &getFields( $fields[$dao->id]['is_required'] = $dao->is_required; $fields[$dao->id]['table_name'] = $dao->table_name; $fields[$dao->id]['column_name'] = $dao->column_name; + $fields[$dao->id]['where'] = $dao->table_name . '.' . $dao->column_name; + // Probably we should use a different fn to get the extends tables but this is a refactor so not changing that now. + $fields[$dao->id]['extends_table'] = array_key_exists($dao->extends, CRM_Core_BAO_CustomQuery::$extendsMap) ? CRM_Core_BAO_CustomQuery::$extendsMap[$dao->extends] : ''; + if (in_array($dao->extends, CRM_Contact_BAO_ContactType::subTypes())) { + // if $extends is a subtype, refer contact table + $fields[$dao->id]['extends_table'] = 'civicrm_contact'; + } + // Search table is used by query object searches.. + $fields[$dao->id]['search_table'] = ($fields[$dao->id]['extends_table'] == 'civicrm_contact') ? 'contact_a' : $fields[$dao->id]['extends_table']; self::getOptionsForField($fields[$dao->id], $dao->option_group_name); } - CRM_Core_BAO_Cache::setItem($fields, - 'contact fields', - "custom importableFields $cacheKey" - ); + Civi::cache('fields')->set("custom importableFields $cacheKey", $fields); } self::$_importFields[$cacheKey] = $fields; } @@ -682,7 +588,7 @@ public static function getFieldsForImport( $checkPermission ); - $importableFields = array(); + $importableFields = []; foreach ($fields as $id => $values) { // for now we should not allow multiple fields in profile / export etc, hence unsetting if (!$search && @@ -690,30 +596,15 @@ public static function getFieldsForImport( ) { continue; } + if (!empty($values['text_length'])) { + $values['maxlength'] = (int) $values['text_length']; + } /* generate the key for the fields array */ $key = "custom_$id"; - - $regexp = preg_replace('/[.,;:!?]/', '', CRM_Utils_Array::value(0, $values)); - $importableFields[$key] = array( - 'name' => $key, - 'title' => CRM_Utils_Array::value('label', $values), - 'headerPattern' => '/' . preg_quote($regexp, '/') . '/', - 'import' => 1, - 'custom_field_id' => $id, - 'options_per_line' => CRM_Utils_Array::value('options_per_line', $values), - 'text_length' => CRM_Utils_Array::value('text_length', $values, 255), - 'data_type' => CRM_Utils_Array::value('data_type', $values), - 'html_type' => CRM_Utils_Array::value('html_type', $values), - 'is_search_range' => CRM_Utils_Array::value('is_search_range', $values), - ); - - // CRM-6681, pass date and time format when html_type = Select Date - if (CRM_Utils_Array::value('html_type', $values) == 'Select Date') { - $importableFields[$key]['date_format'] = CRM_Utils_Array::value('date_format', $values); - $importableFields[$key]['time_format'] = CRM_Utils_Array::value('time_format', $values); - } + $importableFields[$key] = $values; + $importableFields[$key]['import'] = 1; } return $importableFields; @@ -803,6 +694,7 @@ public static function addQuickFormElement( $field = self::getFieldObject($fieldId); $widget = $field->html_type; $element = NULL; + $customFieldAttributes = array(); // Custom field HTML should indicate group+field name $groupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id); @@ -825,7 +717,6 @@ public static function addQuickFormElement( 'Multi-Select State/Province', 'Select Country', 'Multi-Select Country', - 'AdvMulti-Select', 'CheckBox', 'Radio', ))); @@ -834,29 +725,35 @@ public static function addQuickFormElement( $options = $field->getOptions($search ? 'search' : 'create'); // Consolidate widget types to simplify the below switch statement - if ($search || ($widget !== 'AdvMulti-Select' && strpos($widget, 'Select') !== FALSE)) { + if ($search || (strpos($widget, 'Select') !== FALSE)) { $widget = 'Select'; } - $selectAttributes = array( - 'data-crm-custom' => $dataCrmCustomVal, - 'class' => 'crm-select2', - ); + + $customFieldAttributes['data-crm-custom'] = $dataCrmCustomVal; + $selectAttributes = array('class' => 'crm-select2'); + // Search field is always multi-select if ($search || strpos($field->html_type, 'Multi') !== FALSE) { $selectAttributes['class'] .= ' huge'; $selectAttributes['multiple'] = 'multiple'; $selectAttributes['placeholder'] = $placeholder; } + // Add data for popup link. Normally this is handled by CRM_Core_Form->addSelect - if ($field->option_group_id && !$search && $widget == 'Select' && CRM_Core_Permission::check('administer CiviCRM')) { - $selectAttributes += array( + $isSupportedWidget = in_array($widget, ['Select', 'Radio']); + $canEditOptions = CRM_Core_Permission::check('administer CiviCRM'); + if ($field->option_group_id && !$search && $isSelect && $canEditOptions) { + $customFieldAttributes += array( 'data-api-entity' => $field->getEntity(), 'data-api-field' => 'custom_' . $field->id, 'data-option-edit-path' => 'civicrm/admin/options/' . CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $field->option_group_id), ); + $selectAttributes += $customFieldAttributes; } } + $rangeDataTypes = ['Int', 'Float', 'Money']; + if (!isset($label)) { $label = $field->label; } @@ -866,7 +763,7 @@ public static function addQuickFormElement( switch ($widget) { case 'Text': case 'Link': - if ($field->is_search_range && $search) { + if ($field->is_search_range && $search && in_array($field->data_type, $rangeDataTypes)) { $qf->add('text', $elementName . '_from', $label . ' ' . ts('From'), $field->attributes); $qf->add('text', $elementName . '_to', ts('To'), $field->attributes); } @@ -932,65 +829,70 @@ public static function addQuickFormElement( break; case 'Radio': - $choice = array(); - foreach ($options as $v => $l) { - $choice[] = $qf->createElement('radio', NULL, '', $l, (string) $v, $field->attributes); - } - $element = $qf->addGroup($choice, $elementName, $label); - if ($useRequired && !$search) { - $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required'); + if ($field->is_search_range && $search && in_array($field->data_type, $rangeDataTypes)) { + $qf->add('text', $elementName . '_from', $label . ' ' . ts('From'), $field->attributes); + $qf->add('text', $elementName . '_to', ts('To'), $field->attributes); } else { - $element->setAttribute('allowClear', TRUE); + $choice = array(); + parse_str($field->attributes, $radioAttributes); + $radioAttributes = array_merge($radioAttributes, $customFieldAttributes); + + foreach ($options as $v => $l) { + $choice[] = $qf->createElement('radio', NULL, '', $l, (string) $v, $radioAttributes); + } + $element = $qf->addGroup($choice, $elementName, $label); + $optionEditKey = 'data-option-edit-path'; + if (isset($selectAttributes[$optionEditKey])) { + $element->setAttribute($optionEditKey, $selectAttributes[$optionEditKey]); + } + + if ($useRequired && !$search) { + $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required'); + } + else { + $element->setAttribute('allowClear', TRUE); + } } break; // For all select elements case 'Select': - if (empty($selectAttributes['multiple'])) { - $options = array('' => $placeholder) + $options; - } - $element = $qf->add('select', $elementName, $label, $options, $useRequired && !$search, $selectAttributes); - - // Add and/or option for fields that store multiple values - if ($search && self::isSerialized($field)) { - - $operators = array( - $qf->createElement('radio', NULL, '', ts('Any'), 'or', array('title' => ts('Results may contain any of the selected options'))), - $qf->createElement('radio', NULL, '', ts('All'), 'and', array('title' => ts('Results must have all of the selected options'))), - ); - $qf->addGroup($operators, $elementName . '_operator'); - $qf->setDefaults(array($elementName . '_operator' => 'or')); + if ($field->is_search_range && $search && in_array($field->data_type, $rangeDataTypes)) { + $qf->add('text', $elementName . '_from', $label . ' ' . ts('From'), $field->attributes); + $qf->add('text', $elementName . '_to', ts('To'), $field->attributes); } - break; - - case 'AdvMulti-Select': - $element = $qf->addElement( - 'advmultiselect', - $elementName, - $label, $options, - array( - 'size' => 5, - 'style' => '', - 'class' => 'advmultiselect', - 'data-crm-custom' => $dataCrmCustomVal, - ) - ); + else { + if (empty($selectAttributes['multiple'])) { + $options = array('' => $placeholder) + $options; + } + $element = $qf->add('select', $elementName, $label, $options, $useRequired && !$search, $selectAttributes); - $element->setButtonAttributes('add', array('value' => ts('Add >>'))); - $element->setButtonAttributes('remove', array('value' => ts('<< Remove'))); + // Add and/or option for fields that store multiple values + if ($search && self::isSerialized($field)) { - if ($useRequired && !$search) { - $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required'); + $operators = array( + $qf->createElement('radio', NULL, '', ts('Any'), 'or', array('title' => ts('Results may contain any of the selected options'))), + $qf->createElement('radio', NULL, '', ts('All'), 'and', array('title' => ts('Results must have all of the selected options'))), + ); + $qf->addGroup($operators, $elementName . '_operator'); + $qf->setDefaults(array($elementName . '_operator' => 'or')); + } } break; case 'CheckBox': $check = array(); foreach ($options as $v => $l) { - $check[] = &$qf->addElement('advcheckbox', $v, NULL, $l, array('data-crm-custom' => $dataCrmCustomVal)); + $check[] = &$qf->addElement('advcheckbox', $v, NULL, $l, $customFieldAttributes); + } + + $group = $element = $qf->addGroup($check, $elementName, $label); + $optionEditKey = 'data-option-edit-path'; + if (isset($customFieldAttributes[$optionEditKey])) { + $group->setAttribute($optionEditKey, $customFieldAttributes[$optionEditKey]); } - $element = $qf->addGroup($check, $elementName, $label); + if ($useRequired && !$search) { $qf->addRule($elementName, ts('%1 is a required field.', array(1 => $label)), 'required'); } @@ -1045,8 +947,15 @@ public static function addQuickFormElement( $element = $qf->add('text', $elementName, $label, $attributes, $useRequired && !$search); $urlParams = "context=customfield&id={$field->id}"; - - $customUrls[$elementName] = CRM_Utils_System::url('civicrm/ajax/contactref', + $idOfelement = $elementName; + // dev/core#362 if in an onbehalf profile clean up the name to get rid of square brackets that break the select 2 js + // However this caused regression https://lab.civicrm.org/dev/core/issues/619 so it has been hacked back to + // only affecting on behalf - next time someone looks at this code it should be with a view to overhauling it + // rather than layering on more hacks. + if (substr($elementName, 0, 8) === 'onbehalf' && strpos($elementName, '[') && strpos($elementName, ']')) { + $idOfelement = substr(substr($elementName, (strpos($elementName, '[') + 1)), 0, -1); + } + $customUrls[$idOfelement] = CRM_Utils_System::url('civicrm/ajax/contactref', $urlParams, FALSE, NULL, FALSE ); @@ -1055,11 +964,11 @@ public static function addQuickFormElement( else { // FIXME: This won't work with customFieldOptions hook $attributes += array( - 'entity' => 'option_value', + 'entity' => 'OptionValue', 'placeholder' => $placeholder, 'multiple' => $search, 'api' => array( - 'params' => array('option_group_id' => $field->option_group_id), + 'params' => array('option_group_id' => $field->option_group_id, 'is_active' => 1), ), ); $element = $qf->addEntityRef($elementName, $label, $attributes, $useRequired && !$search); @@ -1141,12 +1050,12 @@ public static function deleteField($field) { /** * @param string|int|array|null $value * @param CRM_Core_BAO_CustomField|int|array|string $field - * @param $contactId + * @param int $entityId * * @return string * @throws \Exception */ - public static function displayValue($value, $field, $contactId = NULL) { + public static function displayValue($value, $field, $entityId = NULL) { $field = is_array($field) ? $field['id'] : $field; $fieldId = is_object($field) ? $field->id : (int) str_replace('custom_', '', $field); @@ -1160,7 +1069,7 @@ public static function displayValue($value, $field, $contactId = NULL) { $fieldInfo = array('options' => $field->getOptions()) + (array) $field; - return self::formatDisplayValue($value, $fieldInfo, $contactId); + return self::formatDisplayValue($value, $fieldInfo, $entityId); } /** @@ -1178,8 +1087,8 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { $value = CRM_Utils_Array::explodePadded($value); } // CRM-12989 fix - if ($field['html_type'] == 'CheckBox') { - CRM_Utils_Array::formatArrayKeys($value); + if ($field['html_type'] == 'CheckBox' && $value) { + $value = CRM_Utils_Array::convertCheckboxFormatToArray($value); } $display = is_array($value) ? implode(', ', $value) : (string) $value; @@ -1192,12 +1101,16 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { case 'Select Country': case 'Select State/Province': case 'CheckBox': - case 'AdvMulti-Select': case 'Multi-Select': case 'Multi-Select State/Province': case 'Multi-Select Country': if ($field['data_type'] == 'ContactReference' && $value) { - $display = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'display_name'); + if (is_numeric($value)) { + $display = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'display_name'); + } + else { + $display = $value; + } } elseif (is_array($value)) { $v = array(); @@ -1234,8 +1147,8 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { break; default: - // if time is not selected remove time from value - $value = substr($value, 0, 10); + //If time is not selected remove time from value. + $value = $value ? date('Y-m-d', strtotime($value)) : ''; } $customFormat = implode(" ", $customTimeFormat); } @@ -1247,7 +1160,13 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { // In the context of displaying a profile, show file/image if ($value) { if ($entityId) { - $url = self::getFileURL($entityId, $field['id']); + if (CRM_Utils_Rule::positiveInteger($value)) { + $fileId = $value; + } + else { + $fileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_File', $value, 'id', 'uri'); + } + $url = self::getFileURL($entityId, $field['id'], $fileId); if ($url) { $display = $url['file_url']; } @@ -1256,7 +1175,16 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { // In other contexts show a paperclip icon if (CRM_Utils_Rule::integer($value)) { $icons = CRM_Core_BAO_File::paperIconAttachment('*', $value); - $display = $icons[$value]; + + $file_description = ''; + try { + $file_description = civicrm_api3('File', 'getvalue', ['return' => "description", 'id' => $value]); + } + catch (CiviCRM_API3_Exception $dontcare) { + // don't care + } + + $display = "{$icons[$value]}{$file_description}"; } else { //CRM-18396, if filename is passed instead @@ -1266,13 +1194,17 @@ private static function formatDisplayValue($value, $field, $entityId = NULL) { } break; + case 'Link': + $display = $display ? "$display" : $display; + break; + case 'TextArea': $display = nl2br($display); break; case 'Text': if ($field['data_type'] == 'Money' && isset($value)) { - //$value can also be an array(while using IN operator from search builder or api). + // $value can also be an array(while using IN operator from search builder or api). foreach ((array) $value as $val) { $disp[] = CRM_Utils_Money::format($val, NULL, NULL, TRUE); } @@ -1332,7 +1264,7 @@ public static function setProfileDefaults( if (!$value) { $config = CRM_Core_Config::singleton(); if ($config->defaultContactCountry) { - $value = $config->defaultContactCountry(); + $value = CRM_Core_BAO_Country::defaultContactCountry(); } } } @@ -1351,7 +1283,6 @@ public static function setProfileDefaults( } switch ($customField->html_type) { case 'CheckBox': - case 'AdvMulti-Select': case 'Multi-Select': $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldId, FALSE); $defaults[$elementName] = array(); @@ -1363,27 +1294,13 @@ public static function setProfileDefaults( if ($customField->html_type == 'CheckBox') { $defaults[$elementName][$val['value']] = 1; } - elseif ($customField->html_type == 'Multi-Select' || - $customField->html_type == 'AdvMulti-Select' - ) { + elseif ($customField->html_type == 'Multi-Select') { $defaults[$elementName][$val['value']] = $val['value']; } } } break; - case 'Autocomplete-Select': - if ($customField->data_type == 'ContactReference') { - if (is_numeric($value)) { - $defaults[$elementName . '_id'] = $value; - $defaults[$elementName] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $value, 'sort_name'); - } - } - else { - $defaults[$elementName] = $value; - } - break; - default: $defaults[$elementName] = $value; } @@ -1445,9 +1362,10 @@ public static function getFileURL($contactID, $cfID, $fileID = NULL, $absolute = 'entity_id', 'file_id' ); - list($path) = CRM_Core_BAO_File::path($fileID, $entityId, NULL, NULL); + list($path) = CRM_Core_BAO_File::path($fileID, $entityId); + $fileHash = CRM_Core_BAO_File::generateFileHash($entityId, $fileID); $url = CRM_Utils_System::url('civicrm/file', - "reset=1&id=$fileID&eid=$contactID", + "reset=1&id=$fileID&eid=$entityId&fcs=$fileHash", $absolute, NULL, TRUE, TRUE ); $result['file_url'] = CRM_Utils_File::getFileURL($path, $fileType, $url); @@ -1458,8 +1376,9 @@ public static function getFileURL($contactID, $cfID, $fileID = NULL, $absolute = $fileID, 'uri' ); + $fileHash = CRM_Core_BAO_File::generateFileHash($contactID, $fileID); $url = CRM_Utils_System::url('civicrm/file', - "reset=1&id=$fileID&eid=$contactID", + "reset=1&id=$fileID&eid=$contactID&fcs=$fileHash", $absolute, NULL, TRUE, TRUE ); $result['file_url'] = CRM_Utils_File::getFileURL($uri, $fileType, $url); @@ -1575,9 +1494,7 @@ public static function formatCustomField( } } - if ($customFields[$customFieldId]['html_type'] == 'Multi-Select' || - $customFields[$customFieldId]['html_type'] == 'AdvMulti-Select' - ) { + if ($customFields[$customFieldId]['html_type'] == 'Multi-Select') { if ($value) { $value = CRM_Utils_Array::implodePadded($value); } @@ -1587,7 +1504,6 @@ public static function formatCustomField( } if (($customFields[$customFieldId]['html_type'] == 'Multi-Select' || - $customFields[$customFieldId]['html_type'] == 'AdvMulti-Select' || $customFields[$customFieldId]['html_type'] == 'CheckBox' ) && $customFields[$customFieldId]['data_type'] == 'String' && @@ -1642,7 +1558,7 @@ public static function formatCustomField( $value = 0; } - $fileId = NULL; + $fileID = NULL; if ($customFields[$customFieldId]['data_type'] == 'File') { if (empty($value)) { @@ -1681,20 +1597,20 @@ public static function formatCustomField( FROM $tableName WHERE id = %1"; $params = array(1 => array($customValueId, 'Integer')); - $fileId = CRM_Core_DAO::singleValueQuery($query, $params); + $fileID = CRM_Core_DAO::singleValueQuery($query, $params); } $fileDAO = new CRM_Core_DAO_File(); - if ($fileId) { - $fileDAO->id = $fileId; + if ($fileID) { + $fileDAO->id = $fileID; } $fileDAO->uri = $filename; $fileDAO->mime_type = $mimeType; $fileDAO->upload_date = date('YmdHis'); $fileDAO->save(); - $fileId = $fileDAO->id; + $fileID = $fileDAO->id; $value = $filename; } @@ -1722,7 +1638,7 @@ public static function formatCustomField( 'custom_group_id' => $groupID, 'table_name' => $tableName, 'column_name' => $columnName, - 'file_id' => $fileId, + 'file_id' => $fileID, 'is_multiple' => $customFields[$customFieldId]['is_multiple'], ); @@ -1781,66 +1697,43 @@ public static function defaultCustomTableSchema($params) { * * @param CRM_Core_DAO_CustomField $field * @param string $operation - * @param bool $indexExist - * @param bool $triggerRebuild */ - public static function createField($field, $operation, $indexExist = FALSE, $triggerRebuild = TRUE) { - $tableName = CRM_Core_DAO::getFieldValue( - 'CRM_Core_DAO_CustomGroup', - $field->custom_group_id, - 'table_name' - ); - - $params = array( - 'table_name' => $tableName, - 'operation' => $operation, - 'name' => $field->column_name, - 'type' => CRM_Core_BAO_CustomValueTable::fieldToSQLType( - $field->data_type, - $field->text_length - ), - 'required' => $field->is_required, - 'searchable' => $field->is_searchable, - ); - - if ($operation == 'delete') { - $fkName = "{$tableName}_{$field->column_name}"; - if (strlen($fkName) >= 48) { - $fkName = substr($fkName, 0, 32) . '_' . substr(md5($fkName), 0, 16); + public static function createField($field, $operation) { + $sql = str_repeat(' ', 8); + $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $field->custom_group_id, 'table_name'); + $sql .= "ALTER TABLE " . $tableName; + $sql .= self::getAlterFieldSQL($field, $operation); + + // CRM-7007: do not i18n-rewrite this query + CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, FALSE, FALSE); + + $config = CRM_Core_Config::singleton(); + if ($config->logging) { + // CRM-16717 not sure why this was originally limited to add. + // For example custom tables can have field length changes - which need to flow through to logging. + // Are there any modifies we DON'T was to call this function for (& shouldn't it be clever enough to cope?) + if ($operation === 'add' || $operation === 'modify') { + $logging = new CRM_Logging_Schema(); + $logging->fixSchemaDifferencesFor($tableName, [trim(strtoupper($operation)) => [$field->column_name]]); } - $params['fkName'] = $fkName; - } - if ($field->data_type == 'Country' && $field->html_type == 'Select Country') { - $params['fk_table_name'] = 'civicrm_country'; - $params['fk_field_name'] = 'id'; - $params['fk_attributes'] = 'ON DELETE SET NULL'; - } - elseif ($field->data_type == 'Country' && $field->html_type == 'Multi-Select Country') { - $params['type'] = 'varchar(255)'; - } - elseif ($field->data_type == 'StateProvince' && $field->html_type == 'Select State/Province') { - $params['fk_table_name'] = 'civicrm_state_province'; - $params['fk_field_name'] = 'id'; - $params['fk_attributes'] = 'ON DELETE SET NULL'; - } - elseif ($field->data_type == 'StateProvince' && $field->html_type == 'Multi-Select State/Province') { - $params['type'] = 'varchar(255)'; - } - elseif ($field->data_type == 'File') { - $params['fk_table_name'] = 'civicrm_file'; - $params['fk_field_name'] = 'id'; - $params['fk_attributes'] = 'ON DELETE SET NULL'; - } - elseif ($field->data_type == 'ContactReference') { - $params['fk_table_name'] = 'civicrm_contact'; - $params['fk_field_name'] = 'id'; - $params['fk_attributes'] = 'ON DELETE SET NULL'; - } - if (isset($field->default_value)) { - $params['default'] = "'{$field->default_value}'"; } - CRM_Core_BAO_SchemaHandler::alterFieldSQL($params, $indexExist, $triggerRebuild); + Civi::service('sql_triggers')->rebuild($tableName, TRUE); + } + + /** + * @param CRM_Core_DAO_CustomField $field + * @param string $operation + * + * @return bool + */ + public static function getAlterFieldSQL($field, $operation) { + $indexExist = $operation === 'add' ? FALSE : CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $field->id, 'is_searchable'); + $params = self::prepareCreateParams($field, $operation); + // Let's suppress the required flag, since that can cause an sql issue... for unknown reasons since we are calling + // a function only used by Custom Field creation... + $params['required'] = FALSE; + return CRM_Core_BAO_SchemaHandler::getFieldAlterSQL($params, $indexExist); } /** @@ -1903,9 +1796,7 @@ public static function _moveFieldValidate($fieldID, $newGroupID) { FROM $tableName WHERE $columnName is not null "; - $count = CRM_Core_DAO::singleValueQuery($query, - CRM_Core_DAO::$_nullArray - ); + $count = CRM_Core_DAO::singleValueQuery($query); if ($count > 0) { $query = " SELECT extends @@ -1974,6 +1865,261 @@ public static function moveField($fieldID, $newGroupID) { CRM_Utils_System::flushCache(); } + /** + * Create an option value for a custom field option group ID. + * + * @param array $params + * @param string $value + * @param \CRM_Core_DAO_OptionGroup $optionGroup + * @param string $optionName + * @param string $dataType + */ + protected static function createOptionValue(&$params, $value, CRM_Core_DAO_OptionGroup $optionGroup, $optionName, $dataType) { + if (strlen(trim($value))) { + $optionValue = new CRM_Core_DAO_OptionValue(); + $optionValue->option_group_id = $optionGroup->id; + $optionValue->label = $params['option_label'][$optionName]; + $optionValue->name = CRM_Utils_String::titleToVar($params['option_label'][$optionName]); + switch ($dataType) { + case 'Money': + $optionValue->value = CRM_Utils_Rule::cleanMoney($value); + break; + + case 'Int': + $optionValue->value = intval($value); + break; + + case 'Float': + $optionValue->value = floatval($value); + break; + + default: + $optionValue->value = trim($value); + } + + $optionValue->weight = $params['option_weight'][$optionName]; + $optionValue->is_active = CRM_Utils_Array::value($optionName, $params['option_status'], FALSE); + $optionValue->save(); + } + } + + /** + * Prepare for the create operation. + * + * Munge params, create the option values if needed. + * + * This could be called by a single create or a batchCreate. + * + * @param array $params + * + * @return array + */ + protected static function prepareCreate($params) { + $op = empty($params['id']) ? 'create' : 'edit'; + CRM_Utils_Hook::pre($op, 'CustomField', CRM_Utils_Array::value('id', $params), $params); + $params['is_append_field_id_to_column_name'] = !isset($params['column_name']); + if ($op === 'create') { + CRM_Core_DAO::setCreateDefaults($params, self::getDefaults()); + if (!isset($params['column_name'])) { + // if add mode & column_name not present, calculate it. + $params['column_name'] = strtolower(CRM_Utils_String::munge($params['label'], '_', 32)); + } + if (!isset($params['name'])) { + $params['name'] = CRM_Utils_String::munge($params['label'], '_', 64); + } + } + else { + $params['column_name'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', + $params['id'], + 'column_name' + ); + } + + switch (CRM_Utils_Array::value('html_type', $params)) { + case 'Select Date': + if (empty($params['date_format'])) { + $config = CRM_Core_Config::singleton(); + $params['date_format'] = $config->dateInputFormat; + } + break; + + case 'CheckBox': + case 'Multi-Select': + if (isset($params['default_checkbox_option'])) { + $tempArray = array_keys($params['default_checkbox_option']); + $defaultArray = []; + foreach ($tempArray as $k => $v) { + if ($params['option_value'][$v]) { + $defaultArray[] = $params['option_value'][$v]; + } + } + + if (!empty($defaultArray)) { + // also add the separator before and after the value per new convention (CRM-1604) + $params['default_value'] = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $defaultArray) . CRM_Core_DAO::VALUE_SEPARATOR; + } + } + else { + if (!empty($params['default_option']) && isset($params['option_value'][$params['default_option']]) + ) { + $params['default_value'] = $params['option_value'][$params['default_option']]; + } + } + break; + } + + $htmlType = CRM_Utils_Array::value('html_type', $params); + $dataType = CRM_Utils_Array::value('data_type', $params); + $allowedOptionTypes = ['String', 'Int', 'Float', 'Money']; + + // create any option group & values if required + if ($htmlType != 'Text' && in_array($dataType, $allowedOptionTypes) + ) { + //CRM-16659: if option_value then create an option group for this custom field. + if ($params['option_type'] == 1 && (empty($params['option_group_id']) || !empty($params['option_value']))) { + // first create an option group for this custom group + $optionGroup = new CRM_Core_DAO_OptionGroup(); + $optionGroup->name = "{$params['column_name']}_" . date('YmdHis'); + $optionGroup->title = $params['label']; + $optionGroup->is_active = 1; + // Don't set reserved as it's not a built-in option group and may be useful for other custom fields. + $optionGroup->is_reserved = 0; + $optionGroup->data_type = $dataType; + $optionGroup->save(); + $params['option_group_id'] = $optionGroup->id; + if (!empty($params['option_value']) && is_array($params['option_value'])) { + foreach ($params['option_value'] as $k => $v) { + self::createOptionValue($params, $v, $optionGroup, $k, $dataType); + } + } + } + } + + // check for orphan option groups + if (!empty($params['option_group_id'])) { + if (!empty($params['id'])) { + self::fixOptionGroups($params['id'], $params['option_group_id']); + } + + // if we do not have a default value + // retrieve it from one of the other custom fields which use this option group + if (empty($params['default_value'])) { + //don't insert only value separator as default value, CRM-4579 + $defaultValue = self::getOptionGroupDefault($params['option_group_id'], + $htmlType + ); + + if (!CRM_Utils_System::isNull(explode(CRM_Core_DAO::VALUE_SEPARATOR, + $defaultValue + )) + ) { + $params['default_value'] = $defaultValue; + } + } + } + + // since we need to save option group id :) + if (!isset($params['attributes']) && strtolower($htmlType) == 'textarea') { + $params['attributes'] = 'rows=4, cols=60'; + } + return $params; + } + + /** + * Create database entry for custom field and related option groups. + * + * @param array $params + * + * @return CRM_Core_DAO_CustomField + */ + protected static function createCustomFieldRecord($params) { + $transaction = new CRM_Core_Transaction(); + $params = self::prepareCreate($params); + + $customField = new CRM_Core_DAO_CustomField(); + $customField->copyValues($params); + $customField->save(); + + //create/drop the index when we toggle the is_searchable flag + $op = empty($params['id']) ? 'add' : 'modify'; + if ($op !== 'modify') { + if ($params['is_append_field_id_to_column_name']) { + $params['column_name'] .= "_{$customField->id}"; + } + $customField->column_name = $params['column_name']; + $customField->save(); + } + + // complete transaction - note that any table alterations include an implicit commit so this is largely meaningless. + $transaction->commit(); + + // make sure all values are present in the object for further processing + $customField->find(TRUE); + return $customField; + } + + /** + * Move custom data from one contact to another. + * + * This is currently the start of a refactoring. The theory is each + * entity could have a 'move' function with a DAO default one to fall back on. + * + * At the moment this only does a small part of the process - ie deleting a file field that + * is about to be overwritten. However, the goal is the whole process around the move for + * custom data should be in here. + * + * This is currently called by the merge class but it makes sense that api could + * expose move actions as moving (e.g) contributions feels like a common + * ask that should be handled by the form layer. + * + * @param int $oldContactID + * @param int $newContactID + * @param int[] $fieldIDs + * Optional list field ids to move. + * + * @throws \CiviCRM_API3_Exception + * @throws \Exception + */ + public function move($oldContactID, $newContactID, $fieldIDs) { + if (empty($fieldIDs)) { + return; + } + $fields = civicrm_api3('CustomField', 'get', ['id' => ['IN' => $fieldIDs], 'return' => ['custom_group_id.is_multiple', 'custom_group_id.table_name', 'column_name', 'data_type'], 'options' => ['limit' => 0]])['values']; + $return = []; + foreach ($fieldIDs as $fieldID) { + $return[] = 'custom_' . $fieldID; + } + $oldContact = civicrm_api3('Contact', 'getsingle', ['id' => $oldContactID, 'return' => $return]); + $newContact = civicrm_api3('Contact', 'getsingle', ['id' => $newContactID, 'return' => $return]); + + // The moveAllBelongings function has functionality to move custom fields. It doesn't work very well... + // @todo handle all fields here but more immediately Country since that is broken at the moment. + $fieldTypesNotHandledInMergeAttempt = ['File']; + foreach ($fields as $field) { + $isMultiple = !empty($field['custom_group_id.is_multiple']); + if ($field['data_type'] === 'File' && !$isMultiple) { + if (!empty($oldContact['custom_' . $field['id']]) && !empty($newContact['custom_' . $field['id']])) { + CRM_Core_BAO_File::deleteFileReferences($oldContact['custom_' . $field['id']], $oldContactID, $field['id']); + } + if (!empty($oldContact['custom_' . $field['id']])) { + CRM_Core_DAO::executeQuery(" + UPDATE civicrm_entity_file + SET entity_id = $newContactID + WHERE file_id = {$oldContact['custom_' . $field['id']]}" + ); + } + } + if (in_array($field['data_type'], $fieldTypesNotHandledInMergeAttempt) && !$isMultiple) { + CRM_Core_DAO::executeQuery( + "INSERT INTO {$field['custom_group_id.table_name']} (entity_id, {$field['column_name']}) + VALUES ($newContactID, {$oldContact['custom_' . $field['id']]}) + ON DUPLICATE KEY UPDATE + {$field['column_name']} = {$oldContact['custom_' . $field['id']]} + "); + } + } + } + /** * Get the database table name and column name for a custom field. * @@ -1984,6 +2130,7 @@ public static function moveField($fieldID, $newGroupID) { * * @return array * fatal is fieldID does not exists, else array of tableName, columnName + * @throws \Exception */ public static function getTableColumnGroup($fieldID, $force = FALSE) { $cacheKey = "CRM_Core_DAO_CustomField_CustomGroup_TableColumn_{$fieldID}"; @@ -2002,7 +2149,6 @@ public static function getTableColumnGroup($fieldID, $force = FALSE) { if (!$dao->fetch()) { CRM_Core_Error::fatal(); } - $dao->free(); $fieldValues = array($dao->table_name, $dao->column_name, $dao->id); $cache->set($cacheKey, $fieldValues); } @@ -2012,6 +2158,8 @@ public static function getTableColumnGroup($fieldID, $force = FALSE) { /** * Get custom option groups. * + * @deprecated Use the API OptionGroup.get + * * @param array $includeFieldIds * Ids of custom fields for which option groups must be included. * @@ -2054,6 +2202,24 @@ public static function customOptionGroup($includeFieldIds = NULL) { return $customOptionGroup[$cacheKey]; } + /** + * Get defaults for new entity. + * + * @return array + */ + public static function getDefaults() { + return [ + 'is_required' => FALSE, + 'is_searchable' => FALSE, + 'in_selector' => FALSE, + 'is_search_range' => FALSE, + //CRM-15792 - Custom field gets disabled if is_active not set + // this would ideally be a mysql default. + 'is_active' => TRUE, + 'is_view' => FALSE, + ]; + } + /** * Fix orphan groups. * @@ -2071,7 +2237,7 @@ public static function fixOptionGroups($customFieldId, $optionGroupId) { ); // get the updated option group // if both are same return - if ($currentOptionGroupId == $optionGroupId) { + if (!$currentOptionGroupId || $currentOptionGroupId == $optionGroupId) { return; } @@ -2196,46 +2362,46 @@ public static function postProcess( } /** + * Get custom field ID from field/group name/title. * - */ - - /** - * Get custom field ID. + * @param string $fieldName Field name or label + * @param string|null $groupName (Optional) Group name or label + * @param bool $fullString Whether to return "custom_123" or "123" * - * @param string $fieldLabel - * @param null $groupTitle - * - * @return int|null + * @return string|int|null + * @throws \CiviCRM_API3_Exception */ - public static function getCustomFieldID($fieldLabel, $groupTitle = NULL) { - $params = array(1 => array($fieldLabel, 'String')); - if ($groupTitle) { - $params[2] = array($groupTitle, 'String'); - $sql = " -SELECT f.id -FROM civicrm_custom_field f -INNER JOIN civicrm_custom_group g ON f.custom_group_id = g.id -WHERE ( f.label = %1 OR f.name = %1 ) -AND ( g.title = %2 OR g.name = %2 ) -"; - } - else { - $sql = " -SELECT f.id -FROM civicrm_custom_field f -WHERE ( f.label = %1 OR f.name = %1 ) -"; - } + public static function getCustomFieldID($fieldName, $groupName = NULL, $fullString = FALSE) { + $cacheKey = $groupName . '.' . $fieldName; + if (!isset(Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey])) { + $customFieldParams = [ + 'name' => $fieldName, + 'label' => $fieldName, + 'options' => ['or' => [["name", "label"]]], + ]; + + if ($groupName) { + $customFieldParams['custom_group_id.name'] = $groupName; + $customFieldParams['custom_group_id.title'] = $groupName; + $customFieldParams['options'] = ['or' => [["name", "label"], ["custom_group_id.name", "custom_group_id.title"]]]; + } - $dao = CRM_Core_DAO::executeQuery($sql, $params); - if ($dao->fetch() && - $dao->N == 1 - ) { - return $dao->id; + $field = civicrm_api3('CustomField', 'get', $customFieldParams); + + if (empty($field['id'])) { + Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['id'] = NULL; + Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['string'] = NULL; + } + else { + Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['id'] = $field['id']; + Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['string'] = 'custom_' . $field['id']; + } } - else { - return NULL; + + if ($fullString) { + return Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['string']; } + return Civi::$statics['CRM_Core_BAO_CustomField'][$cacheKey]['id']; } /** @@ -2404,6 +2570,31 @@ public static function isMultiRecordField($customId) { return $isMultipleWithGid; } + /** + * Does this field type have any select options? + * + * @param array $field + * + * @return bool + */ + public static function hasOptions($field) { + // Fields retrieved via api are an array, or from the dao are an object. We'll accept either. + $field = (array) $field; + // This will include boolean fields with Yes/No options. + if (in_array($field['html_type'], ['Radio', 'CheckBox'])) { + return TRUE; + } + // Do this before the "Select" string search because date fields have a "Select Date" html_type + // and contactRef fields have an "Autocomplete-Select" html_type - contacts are an FK not an option list. + if (in_array($field['data_type'], ['ContactReference', 'Date'])) { + return FALSE; + } + if (strpos($field['html_type'], 'Select') !== FALSE) { + return TRUE; + } + return !empty($field['option_group_id']); + } + /** * Does this field store a serialized string? * @@ -2463,4 +2654,68 @@ private static function getOptionsForField(&$field, $optionGroupName) { } } + /** + * @param CRM_Core_DAO_CustomField $field + * @param 'add|modify' $operation + * + * @return array + */ + protected static function prepareCreateParams($field, $operation) { + $tableName = CRM_Core_DAO::getFieldValue( + 'CRM_Core_DAO_CustomGroup', + $field->custom_group_id, + 'table_name' + ); + + $params = [ + 'table_name' => $tableName, + 'operation' => $operation, + 'name' => $field->column_name, + 'type' => CRM_Core_BAO_CustomValueTable::fieldToSQLType( + $field->data_type, + $field->text_length + ), + 'required' => $field->is_required, + 'searchable' => $field->is_searchable, + ]; + + if ($operation == 'delete') { + $fkName = "{$tableName}_{$field->column_name}"; + if (strlen($fkName) >= 48) { + $fkName = substr($fkName, 0, 32) . '_' . substr(md5($fkName), 0, 16); + } + $params['fkName'] = $fkName; + } + if ($field->data_type == 'Country' && $field->html_type == 'Select Country') { + $params['fk_table_name'] = 'civicrm_country'; + $params['fk_field_name'] = 'id'; + $params['fk_attributes'] = 'ON DELETE SET NULL'; + } + elseif ($field->data_type == 'Country' && $field->html_type == 'Multi-Select Country') { + $params['type'] = 'varchar(255)'; + } + elseif ($field->data_type == 'StateProvince' && $field->html_type == 'Select State/Province') { + $params['fk_table_name'] = 'civicrm_state_province'; + $params['fk_field_name'] = 'id'; + $params['fk_attributes'] = 'ON DELETE SET NULL'; + } + elseif ($field->data_type == 'StateProvince' && $field->html_type == 'Multi-Select State/Province') { + $params['type'] = 'varchar(255)'; + } + elseif ($field->data_type == 'File') { + $params['fk_table_name'] = 'civicrm_file'; + $params['fk_field_name'] = 'id'; + $params['fk_attributes'] = 'ON DELETE SET NULL'; + } + elseif ($field->data_type == 'ContactReference') { + $params['fk_table_name'] = 'civicrm_contact'; + $params['fk_field_name'] = 'id'; + $params['fk_attributes'] = 'ON DELETE SET NULL'; + } + if (isset($field->default_value)) { + $params['default'] = "'{$field->default_value}'"; + } + return $params; + } + } diff --git a/CRM/Core/BAO/CustomGroup.php b/CRM/Core/BAO/CustomGroup.php index 69a1bd661179..3f92e2696b4d 100644 --- a/CRM/Core/BAO/CustomGroup.php +++ b/CRM/Core/BAO/CustomGroup.php @@ -1,9 +1,9 @@ title = $params['title']; } - if (in_array($params['extends'][0], - array( - 'ParticipantRole', - 'ParticipantEventName', - 'ParticipantEventType', - ) - )) { + $extends = CRM_Utils_Array::value('extends', $params, []); + $extendsEntity = CRM_Utils_Array::value(0, $extends); + + $participantEntities = [ + 'ParticipantRole', + 'ParticipantEventName', + 'ParticipantEventType', + ]; + + if (in_array($extendsEntity, $participantEntities)) { $group->extends = 'Participant'; } else { - $group->extends = $params['extends'][0]; + $group->extends = $extendsEntity; } $group->extends_entity_column_id = 'null'; - if ( - $params['extends'][0] == 'ParticipantRole' || - $params['extends'][0] == 'ParticipantEventName' || - $params['extends'][0] == 'ParticipantEventType' + if (in_array($extendsEntity, $participantEntities) ) { - $group->extends_entity_column_id = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $params['extends'][0], 'value', 'name'); + $group->extends_entity_column_id = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $extendsEntity, 'value', 'name'); } - //this is format when form get submit. - $extendsChildType = CRM_Utils_Array::value(1, $params['extends']); - //lets allow user to pass direct child type value, CRM-6893 + // this is format when form get submit. + $extendsChildType = CRM_Utils_Array::value(1, $extends); + // lets allow user to pass direct child type value, CRM-6893 if (!empty($params['extends_entity_column_value'])) { $extendsChildType = $params['extends_entity_column_value']; } if (!CRM_Utils_System::isNull($extendsChildType)) { $extendsChildType = implode(CRM_Core_DAO::VALUE_SEPARATOR, $extendsChildType); - if (CRM_Utils_Array::value(0, $params['extends']) == 'Relationship') { - $extendsChildType = str_replace(array('_a_b', '_b_a'), array( + if (CRM_Utils_Array::value(0, $extends) == 'Relationship') { + $extendsChildType = str_replace(['_a_b', '_b_a'], [ '', '', - ), $extendsChildType); + ], $extendsChildType); } if (substr($extendsChildType, 0, 1) != CRM_Core_DAO::VALUE_SEPARATOR) { $extendsChildType = CRM_Core_DAO::VALUE_SEPARATOR . $extendsChildType . @@ -113,42 +114,49 @@ public static function create(&$params) { $oldWeight = 0; } $group->weight = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomGroup', $oldWeight, CRM_Utils_Array::value('weight', $params, FALSE)); - $fields = array( + $fields = [ 'style', 'collapse_display', 'collapse_adv_display', 'help_pre', 'help_post', 'is_active', - 'is_public', 'is_multiple', - ); + ]; + $current_db_version = CRM_Core_DAO::singleValueQuery("SELECT version FROM civicrm_domain WHERE id = " . CRM_Core_Config::domainID()); + $is_public_version = $current_db_version >= '4.7.19' ? 1 : 0; + if ($is_public_version) { + $fields[] = 'is_public'; + } foreach ($fields as $field) { - if (isset($params[$field]) || $field == 'is_multiple') { - $group->$field = CRM_Utils_Array::value($field, $params, FALSE); + if (isset($params[$field])) { + $group->$field = $params[$field]; } } $group->max_multiple = isset($params['is_multiple']) ? (isset($params['max_multiple']) && $params['max_multiple'] >= '0' ) ? $params['max_multiple'] : 'null' : 'null'; - $tableName = $oldTableName = NULL; + $tableName = $tableNameNeedingIndexUpdate = NULL; if (isset($params['id'])) { $group->id = $params['id']; - //check whether custom group was changed from single-valued to multiple-valued - $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', - $params['id'], - 'is_multiple' - ); - if ((!empty($params['is_multiple']) || $isMultiple) && - ($params['is_multiple'] != $isMultiple) - ) { - $oldTableName = CRM_Core_DAO::getFieldValue( - 'CRM_Core_DAO_CustomGroup', + if (isset($params['is_multiple'])) { + // check whether custom group was changed from single-valued to multiple-valued + $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $params['id'], - 'table_name' + 'is_multiple' ); + + // dev/core#227 Fix issue where is_multiple in params maybe an empty string if checkbox is not rendered on the form. + $paramsIsMultiple = empty($params['is_multiple']) ? 0 : 1; + if ($paramsIsMultiple != $isMultiple) { + $tableNameNeedingIndexUpdate = CRM_Core_DAO::getFieldValue( + 'CRM_Core_DAO_CustomGroup', + $params['id'], + 'table_name' + ); + } } } else { @@ -168,7 +176,7 @@ public static function create(&$params) { if (CRM_Core_DAO_AllCoreTables::isCoreTable($tableName)) { // Bad idea. Prevent group creation because it might lead to a broken configuration. - CRM_Core_Error::fatal(ts("Cannot create custom table because %1 is already a core table.", array('1' => $tableName))); + CRM_Core_Error::fatal(ts("Cannot create custom table because %1 is already a core table.", ['1' => $tableName])); } } } @@ -185,7 +193,7 @@ public static function create(&$params) { $group->save(); if (!isset($params['id'])) { if (!isset($params['table_name'])) { - $munged_title = strtolower(CRM_Utils_String::munge($group->title, '_', 42)); + $munged_title = strtolower(CRM_Utils_String::munge($group->title, '_', 13)); $tableName = "civicrm_value_{$munged_title}_{$group->id}"; } $group->table_name = $tableName; @@ -198,8 +206,8 @@ public static function create(&$params) { // now create the table associated with this group self::createTable($group); } - elseif ($oldTableName) { - CRM_Core_BAO_SchemaHandler::changeUniqueToIndex($oldTableName, CRM_Utils_Array::value('is_multiple', $params)); + elseif ($tableNameNeedingIndexUpdate) { + CRM_Core_BAO_SchemaHandler::changeUniqueToIndex($tableNameNeedingIndexUpdate, CRM_Utils_Array::value('is_multiple', $params)); } if (CRM_Utils_Array::value('overrideFKConstraint', $params) == 1) { @@ -207,7 +215,7 @@ public static function create(&$params) { $params['id'], 'table_name' ); - CRM_Core_BAO_SchemaHandler::changeFKConstraint($table, self::mapTableName($params['extends'][0])); + CRM_Core_BAO_SchemaHandler::changeFKConstraint($table, self::mapTableName($extendsEntity)); } $transaction->commit(); @@ -246,12 +254,14 @@ public static function retrieve(&$params, &$defaults) { * @param bool $is_active * Value we want to set the is_active field. * - * @return Object - * DAO object on success, null otherwise + * @return bool + * true if we found and updated the object, else false */ public static function setIsActive($id, $is_active) { // reset the cache - CRM_Core_BAO_Cache::deleteGroup('contact fields'); + Civi::cache('fields')->flush(); + // reset ACL and system caches. + CRM_Core_BAO_Cache::resetCaches(); if (!$is_active) { CRM_Core_BAO_UFField::setUFFieldStatus($id, $is_active); @@ -295,16 +305,17 @@ public static function autoCreateByActivityType($activityTypeId) { if (self::hasCustomGroup('Activity', NULL, $activityTypeId)) { return TRUE; } - $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); // everything - $params = array( + // everything + $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE); + $params = [ 'version' => 3, 'extends' => 'Activity', 'extends_entity_column_id' => NULL, - 'extends_entity_column_value' => CRM_Utils_Array::implodePadded(array($activityTypeId)), - 'title' => ts('%1 Questions', array(1 => $activityTypes[$activityTypeId])), + 'extends_entity_column_value' => CRM_Utils_Array::implodePadded([$activityTypeId]), + 'title' => ts('%1 Questions', [1 => $activityTypes[$activityTypeId]]), 'style' => 'Inline', 'is_active' => 1, - ); + ]; $result = civicrm_api('CustomGroup', 'create', $params); return !$result['is_error']; } @@ -317,8 +328,8 @@ public static function autoCreateByActivityType($activityTypeId) { * * @param string $entityType * Of the contact whose contact type is needed. - * @param CRM_Core_Form $deprecated - * Not used. + * @param array $toReturn + * What data should be returned. ['custom_group' => ['id', 'name', etc.], 'custom_field' => ['id', 'label', etc.]] * @param int $entityID * @param int $groupID * @param array $subTypes @@ -331,7 +342,7 @@ public static function autoCreateByActivityType($activityTypeId) { * api - through which it is properly tested - so can be refactored with some comfort.) * * @param bool $checkPermission - * @param varchar $singleRecord + * @param string|int $singleRecord * holds 'new' or id if view/edit/copy form for a single record is being loaded. * @param bool $showPublicOnly * @@ -346,13 +357,15 @@ public static function autoCreateByActivityType($activityTypeId) { * @todo - review this - It also returns an array called 'info' with tables, select, from, where keys * The reason for the info array in unclear and it could be determined from parsing the group tree after creation * With caching the performance impact would be small & the function would be cleaner + * + * @throws \CRM_Core_Exception */ public static function getTree( $entityType, - $deprecated = NULL, + $toReturn = [], $entityID = NULL, $groupID = NULL, - $subTypes = array(), + $subTypes = [], $subName = NULL, $fromCache = TRUE, $onlySubType = NULL, @@ -366,7 +379,7 @@ public static function getTree( } if (!is_array($subTypes)) { if (empty($subTypes)) { - $subTypes = array(); + $subTypes = []; } else { if (stristr($subTypes, ',')) { @@ -379,12 +392,12 @@ public static function getTree( } // create a new tree - $strWhere = $orderBy = ''; - // using tableData to build the queryString - $tableData = array( - 'civicrm_custom_field' => array( + // legacy hardcoded list of data to return + $tableData = [ + 'custom_field' => [ 'id', + 'name', 'label', 'column_name', 'data_type', @@ -402,8 +415,8 @@ public static function getTree( 'time_format', 'option_group_id', 'in_selector', - ), - 'civicrm_custom_group' => array( + ], + 'custom_group' => [ 'id', 'name', 'table_name', @@ -417,16 +430,32 @@ public static function getTree( 'extends_entity_column_id', 'extends_entity_column_value', 'max_multiple', - 'is_public', - ), - ); + ], + ]; + $current_db_version = CRM_Core_DAO::singleValueQuery("SELECT version FROM civicrm_domain WHERE id = " . CRM_Core_Config::domainID()); + $is_public_version = $current_db_version >= '4.7.19' ? 1 : 0; + if ($is_public_version) { + $tableData['custom_group'][] = 'is_public'; + } + if (!$toReturn || !is_array($toReturn)) { + $toReturn = $tableData; + } + else { + // Supply defaults and remove unknown array keys + $toReturn = array_intersect_key(array_filter($toReturn) + $tableData, $tableData); + // Merge in required fields that we must have + $toReturn['custom_field'] = array_unique(array_merge($toReturn['custom_field'], ['id', 'column_name', 'data_type'])); + $toReturn['custom_group'] = array_unique(array_merge($toReturn['custom_group'], ['id', 'is_multiple', 'table_name', 'name'])); + // Validate return fields + $toReturn['custom_field'] = array_intersect($toReturn['custom_field'], array_keys(CRM_Core_DAO_CustomField::fieldKeys())); + $toReturn['custom_group'] = array_intersect($toReturn['custom_group'], array_keys(CRM_Core_DAO_CustomGroup::fieldKeys())); + } // create select - $select = array(); - foreach ($tableData as $tableName => $tableColumn) { + $select = []; + foreach ($toReturn as $tableName => $tableColumn) { foreach ($tableColumn as $columnName) { - $alias = $tableName . "_" . $columnName; - $select[] = "{$tableName}.{$columnName} as {$tableName}_{$columnName}"; + $select[] = "civicrm_{$tableName}.{$columnName} as civicrm_{$tableName}_{$columnName}"; } } $strSelect = "SELECT " . implode(', ', $select); @@ -452,11 +481,14 @@ public static function getTree( $in = "'$entityType'"; } + $params = []; + $sqlParamKey = 1; + $subType = ''; if (!empty($subTypes)) { foreach ($subTypes as $key => $subType) { $subTypeClauses[] = self::whereListHas("civicrm_custom_group.extends_entity_column_value", self::validateSubTypeByEntity($entityType, $subType)); } - $subTypeClause = '(' . implode(' OR ', $subTypeClauses) . ')'; + $subTypeClause = '(' . implode(' OR ', $subTypeClauses) . ')'; if (!$onlySubType) { $subTypeClause = '(' . $subTypeClause . ' OR civicrm_custom_group.extends_entity_column_value IS NULL )'; } @@ -468,7 +500,9 @@ public static function getTree( AND $subTypeClause "; if ($subName) { - $strWhere .= " AND civicrm_custom_group.extends_entity_column_id = {$subName} "; + $strWhere .= " AND civicrm_custom_group.extends_entity_column_id = %{$sqlParamKey}"; + $params[$sqlParamKey] = [$subName, 'String']; + $sqlParamKey = $sqlParamKey + 1; } } else { @@ -482,11 +516,10 @@ public static function getTree( } } - $params = array(); if ($groupID > 0) { // since we want a specific group id we add it to the where clause - $strWhere .= " AND civicrm_custom_group.id = %1"; - $params[1] = array($groupID, 'Integer'); + $strWhere .= " AND civicrm_custom_group.id = %{$sqlParamKey}"; + $params[$sqlParamKey] = [$groupID, 'Integer']; } elseif (!$groupID) { // since groupID is false we need to show all Inline groups @@ -500,7 +533,7 @@ public static function getTree( ); } - if ($showPublicOnly) { + if ($showPublicOnly && $is_public_version) { $strWhere .= "AND civicrm_custom_group.is_public = 1"; } @@ -526,82 +559,21 @@ public static function getTree( $cacheKey = "CRM_Core_DAO_CustomGroup_Query " . md5($cacheString); $multipleFieldGroupCacheKey = "CRM_Core_DAO_CustomGroup_QueryMultipleFields " . md5($cacheString); $cache = CRM_Utils_Cache::singleton(); - $tablesWithEntityData = array(); if ($fromCache) { $groupTree = $cache->get($cacheKey); $multipleFieldGroups = $cache->get($multipleFieldGroupCacheKey); } if (empty($groupTree)) { - $groupTree = $multipleFieldGroups = array(); - $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params); - $customValueTables = array(); - - // process records - while ($crmDAO->fetch()) { - // get the id's - $groupID = $crmDAO->civicrm_custom_group_id; - $fieldId = $crmDAO->civicrm_custom_field_id; - if ($crmDAO->civicrm_custom_group_is_multiple) { - $multipleFieldGroups[$groupID] = $crmDAO->civicrm_custom_group_table_name; - } - // create an array for groups if it does not exist - if (!array_key_exists($groupID, $groupTree)) { - $groupTree[$groupID] = array(); - $groupTree[$groupID]['id'] = $groupID; - - // populate the group information - foreach ($tableData['civicrm_custom_group'] as $fieldName) { - $fullFieldName = "civicrm_custom_group_$fieldName"; - if ($fieldName == 'id' || - is_null($crmDAO->$fullFieldName) - ) { - continue; - } - // CRM-5507 - // This is an old bit of code - per the CRM number & probably does not work reliably if - // that one contact sub-type exists. - if ($fieldName == 'extends_entity_column_value' && !empty($subTypes[0])) { - $groupTree[$groupID]['subtype'] = self::validateSubTypeByEntity($entityType, $subType); - } - $groupTree[$groupID][$fieldName] = $crmDAO->$fullFieldName; - } - $groupTree[$groupID]['fields'] = array(); - - $customValueTables[$crmDAO->civicrm_custom_group_table_name] = array(); - } - - // add the fields now (note - the query row will always contain a field) - // we only reset this once, since multiple values come is as multiple rows - if (!array_key_exists($fieldId, $groupTree[$groupID]['fields'])) { - $groupTree[$groupID]['fields'][$fieldId] = array(); - } - - $customValueTables[$crmDAO->civicrm_custom_group_table_name][$crmDAO->civicrm_custom_field_column_name] = 1; - $groupTree[$groupID]['fields'][$fieldId]['id'] = $fieldId; - // populate information for a custom field - foreach ($tableData['civicrm_custom_field'] as $fieldName) { - $fullFieldName = "civicrm_custom_field_$fieldName"; - if ($fieldName == 'id' || - is_null($crmDAO->$fullFieldName) - ) { - continue; - } - $groupTree[$groupID]['fields'][$fieldId][$fieldName] = $crmDAO->$fullFieldName; - } - } - - if (!empty($customValueTables)) { - $groupTree['info'] = array('tables' => $customValueTables); - } + list($multipleFieldGroups, $groupTree) = self::buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType); $cache->set($cacheKey, $groupTree); $cache->set($multipleFieldGroupCacheKey, $multipleFieldGroups); } - //entitySelectClauses is an array of select clauses for custom value tables which are not multiple + // entitySelectClauses is an array of select clauses for custom value tables which are not multiple // and have data for the given entities. $entityMultipleSelectClauses is the same for ones with multiple - $entitySingleSelectClauses = $entityMultipleSelectClauses = $groupTree['info']['select'] = array(); - $singleFieldTables = array(); + $entitySingleSelectClauses = $entityMultipleSelectClauses = $groupTree['info']['select'] = []; + $singleFieldTables = []; // now that we have all the groups and fields, lets get the values // since we need to know the table and field names // add info to groupTree @@ -609,15 +581,15 @@ public static function getTree( if (isset($groupTree['info']) && !empty($groupTree['info']) && !empty($groupTree['info']['tables']) && $singleRecord != 'new' ) { - $select = $from = $where = array(); + $select = $from = $where = []; $groupTree['info']['where'] = NULL; foreach ($groupTree['info']['tables'] as $table => $fields) { $groupTree['info']['from'][] = $table; - $select = array( + $select = [ "{$table}.id as {$table}_id", "{$table}.entity_id as {$table}_entity_id", - ); + ]; foreach ($fields as $column => $dontCare) { $select[] = "{$table}.{$column} as {$table}_{$column}"; } @@ -656,7 +628,6 @@ public static function getTree( * * @return string * @throws \CRM_Core_Exception - * @throws \CiviCRM_API3_Exception */ protected static function validateSubTypeByEntity($entityType, $subType) { $subType = trim($subType, CRM_Core_DAO::VALUE_SEPARATOR); @@ -665,7 +636,7 @@ protected static function validateSubTypeByEntity($entityType, $subType) { } $contactTypes = CRM_Contact_BAO_ContactType::basicTypeInfo(TRUE); - $contactTypes = array_merge($contactTypes, array('Event' => 1)); + $contactTypes = array_merge($contactTypes, ['Event' => 1]); if ($entityType != 'Contact' && !array_key_exists($entityType, $contactTypes)) { throw new CRM_Core_Exception('Invalid Entity Filter'); @@ -688,8 +659,9 @@ protected static function validateSubTypeByEntity($entityType, $subType) { * @return string * SQL condition. */ - static private function whereListHas($column, $value, $delimiter = CRM_Core_DAO::VALUE_SEPARATOR) { - $bareValue = trim($value, $delimiter); // ? + private static function whereListHas($column, $value, $delimiter = CRM_Core_DAO::VALUE_SEPARATOR) { + // ? + $bareValue = trim($value, $delimiter); $escapedValue = CRM_Utils_Type::escape("%{$delimiter}{$bareValue}{$delimiter}%", 'String', FALSE); return "($column LIKE \"$escapedValue\")"; } @@ -697,7 +669,6 @@ static private function whereListHas($column, $value, $delimiter = CRM_Core_DAO: /** * Check whether the custom group has any data for the given entity. * - * * @param int $entityID * Id of entity for whom we are checking data for. * @param string $table @@ -708,7 +679,7 @@ static private function whereListHas($column, $value, $delimiter = CRM_Core_DAO: * @return bool * does this entity have data in this custom table */ - static public function customGroupDataExistsForEntity($entityID, $table, $getCount = FALSE) { + public static function customGroupDataExistsForEntity($entityID, $table, $getCount = FALSE) { $query = " SELECT count(id) FROM $table @@ -737,7 +708,7 @@ static public function customGroupDataExistsForEntity($entityID, $table, $getCou * @param array $singleFieldTablesWithEntityData * Array of tables in which this entity has data. */ - static public function buildEntityTreeSingleFields(&$groupTree, $entityID, $entitySingleSelectClauses, $singleFieldTablesWithEntityData) { + public static function buildEntityTreeSingleFields(&$groupTree, $entityID, $entitySingleSelectClauses, $singleFieldTablesWithEntityData) { $select = implode(', ', $entitySingleSelectClauses); $fromSQL = " (SELECT $entityID as entity_id ) as first "; foreach ($singleFieldTablesWithEntityData as $table) { @@ -765,10 +736,10 @@ static public function buildEntityTreeSingleFields(&$groupTree, $entityID, $enti * Array of select clauses relevant to the entity. * @param array $multipleFieldTablesWithEntityData * Array of tables in which this entity has data. - * @param varchar $singleRecord + * @param string|int $singleRecord * holds 'new' or id if view/edit/copy form for a single record is being loaded. */ - static public function buildEntityTreeMultipleFields(&$groupTree, $entityID, $entityMultipleSelectClauses, $multipleFieldTablesWithEntityData, $singleRecord = NULL) { + public static function buildEntityTreeMultipleFields(&$groupTree, $entityID, $entityMultipleSelectClauses, $multipleFieldTablesWithEntityData, $singleRecord = NULL) { foreach ($entityMultipleSelectClauses as $table => $selectClauses) { $select = implode(',', $selectClauses); $query = " @@ -780,7 +751,7 @@ static public function buildEntityTreeMultipleFields(&$groupTree, $entityID, $en $offset = $singleRecord - 1; $query .= " LIMIT {$offset}, 1"; } - self::buildTreeEntityDataFromQuery($groupTree, $query, array($table), $singleRecord); + self::buildTreeEntityDataFromQuery($groupTree, $query, [$table], $singleRecord); } } @@ -796,10 +767,10 @@ static public function buildEntityTreeMultipleFields(&$groupTree, $entityID, $en * @param array $includedTables * Tables to include - required because the function (for historical reasons). * iterates through the group tree - * @param varchar $singleRecord + * @param string|int $singleRecord * holds 'new' OR id if view/edit/copy form for a single record is being loaded. */ - static public function buildTreeEntityDataFromQuery(&$groupTree, $query, $includedTables, $singleRecord = NULL) { + public static function buildTreeEntityDataFromQuery(&$groupTree, $query, $includedTables, $singleRecord = NULL) { $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { foreach ($groupTree as $groupID => $group) { @@ -833,10 +804,10 @@ static public function buildTreeEntityDataFromQuery(&$groupTree, $query, $includ * Custom group ID. * @param int $fieldID * Custom field ID. - * @param varchar $singleRecord + * @param string|int $singleRecord * holds 'new' or id if loading view/edit/copy for a single record. */ - static public function buildCustomFieldData($dao, &$groupTree, $table, $groupID, $fieldID, $singleRecord = NULL) { + public static function buildCustomFieldData($dao, &$groupTree, $table, $groupID, $fieldID, $singleRecord = NULL) { $column = $groupTree[$groupID]['fields'][$fieldID]['column_name']; $idName = "{$table}_id"; $fieldName = "{$table}_{$column}"; @@ -849,28 +820,30 @@ static public function buildCustomFieldData($dao, &$groupTree, $table, $groupID, if ($fileDAO->find(TRUE)) { $entityIDName = "{$table}_entity_id"; + $fileHash = CRM_Core_BAO_File::generateFileHash($dao->$entityIDName, $fileDAO->id); $customValue['id'] = $dao->$idName; $customValue['data'] = $fileDAO->uri; $customValue['fid'] = $fileDAO->id; - $customValue['fileURL'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$fileDAO->id}&eid={$dao->$entityIDName}"); + $customValue['fileURL'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$fileDAO->id}&eid={$dao->$entityIDName}&fcs=$fileHash"); $customValue['displayURL'] = NULL; $deleteExtra = ts('Are you sure you want to delete attached file.'); - $deleteURL = array( - CRM_Core_Action::DELETE => array( + $deleteURL = [ + CRM_Core_Action::DELETE => [ 'name' => ts('Delete Attached File'), 'url' => 'civicrm/file', - 'qs' => 'reset=1&id=%%id%%&eid=%%eid%%&fid=%%fid%%&action=delete', + 'qs' => 'reset=1&id=%%id%%&eid=%%eid%%&fid=%%fid%%&action=delete&fcs=%%fcs%%', 'extra' => 'onclick = "if (confirm( \'' . $deleteExtra . '\' ) ) this.href+=\'&confirmed=1\'; else return false;"', - ), - ); + ], + ]; $customValue['deleteURL'] = CRM_Core_Action::formLink($deleteURL, CRM_Core_Action::DELETE, - array( + [ 'id' => $fileDAO->id, 'eid' => $dao->$entityIDName, 'fid' => $fieldID, - ), + 'fcs' => $fileHash, + ], ts('more'), FALSE, 'file.manage.delete', @@ -893,7 +866,7 @@ static public function buildCustomFieldData($dao, &$groupTree, $table, $groupID, ); $customValue['imageURL'] = str_replace('persist/contribute', 'custom', $config->imageUploadURL) . $fileDAO->uri; - list($path) = CRM_Core_BAO_File::path($fileDAO->id, $entityId, NULL, NULL); + list($path) = CRM_Core_BAO_File::path($fileDAO->id, $entityId); if ($path && file_exists($path)) { list($imageWidth, $imageHeight) = getimagesize($path); list($imageThumbWidth, $imageThumbHeight) = CRM_Contact_BAO_Contact::getThumbSize($imageWidth, $imageHeight); @@ -904,27 +877,27 @@ static public function buildCustomFieldData($dao, &$groupTree, $table, $groupID, } } else { - $customValue = array( + $customValue = [ 'id' => $dao->$idName, 'data' => '', - ); + ]; } } else { - $customValue = array( + $customValue = [ 'id' => $dao->$idName, 'data' => $dao->$fieldName, - ); + ]; } if (!array_key_exists('customValue', $groupTree[$groupID]['fields'][$fieldID])) { - $groupTree[$groupID]['fields'][$fieldID]['customValue'] = array(); + $groupTree[$groupID]['fields'][$fieldID]['customValue'] = []; } if (empty($groupTree[$groupID]['fields'][$fieldID]['customValue']) && !empty($singleRecord)) { - $groupTree[$groupID]['fields'][$fieldID]['customValue'] = array($singleRecord => $customValue); + $groupTree[$groupID]['fields'][$fieldID]['customValue'] = [$singleRecord => $customValue]; } elseif (empty($groupTree[$groupID]['fields'][$fieldID]['customValue'])) { - $groupTree[$groupID]['fields'][$fieldID]['customValue'] = array(1 => $customValue); + $groupTree[$groupID]['fields'][$fieldID]['customValue'] = [1 => $customValue]; } else { $groupTree[$groupID]['fields'][$fieldID]['customValue'][] = $customValue; @@ -956,21 +929,18 @@ public static function getTitle($id) { * @param array $extends * Which table does it extend if any. * - * @param null $inSelector + * @param bool $inSelector * * @return array * array consisting of all group and field details */ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$extends = NULL, $inSelector = NULL) { // create a new tree - $groupTree = array(); - $select = $from = $where = $orderBy = ''; - - $tableData = array(); + $groupTree = []; // using tableData to build the queryString - $tableData = array( - 'civicrm_custom_field' => array( + $tableData = [ + 'civicrm_custom_field' => [ 'id', 'label', 'data_type', @@ -993,8 +963,8 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex 'is_view', 'option_group_id', 'in_selector', - ), - 'civicrm_custom_group' => array( + ], + 'civicrm_custom_group' => [ 'id', 'name', 'title', @@ -1006,26 +976,25 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex 'extends_entity_column_value', 'table_name', 'is_multiple', - ), - ); + ], + ]; // create select - $select = "SELECT"; - $s = array(); + $s = []; foreach ($tableData as $tableName => $tableColumn) { foreach ($tableColumn as $columnName) { $s[] = "{$tableName}.{$columnName} as {$tableName}_{$columnName}"; } } $select = 'SELECT ' . implode(', ', $s); - $params = array(); + $params = []; // from, where, order by $from = " FROM civicrm_custom_field, civicrm_custom_group"; $where = " WHERE civicrm_custom_field.custom_group_id = civicrm_custom_group.id AND civicrm_custom_group.is_active = 1 AND civicrm_custom_field.is_active = 1 "; if ($groupId) { - $params[1] = array($groupId, 'Integer'); + $params[1] = [$groupId, 'Integer']; $where .= " AND civicrm_custom_group.id = %1"; } @@ -1038,7 +1007,7 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex } if ($extends) { - $clause = array(); + $clause = []; foreach ($extends as $e) { $clause[] = "civicrm_custom_group.extends = '$e'"; } @@ -1048,7 +1017,7 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex if (in_array('Activity', $extends)) { $extendValues = implode(',', array_keys(CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE))); $where .= " AND ( civicrm_custom_group.extends_entity_column_value IS NULL OR REPLACE( civicrm_custom_group.extends_entity_column_value, %2, ' ') IN ($extendValues) ) "; - $params[2] = array(CRM_Core_DAO::VALUE_SEPARATOR, 'String'); + $params[2] = [CRM_Core_DAO::VALUE_SEPARATOR, 'String']; } } @@ -1073,7 +1042,7 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex // create an array for groups if it does not exist if (!array_key_exists($groupId, $groupTree)) { - $groupTree[$groupId] = array(); + $groupTree[$groupId] = []; $groupTree[$groupId]['id'] = $groupId; foreach ($tableData['civicrm_custom_group'] as $v) { @@ -1086,11 +1055,11 @@ public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$ex $groupTree[$groupId][$v] = $crmDAO->$fullField; } - $groupTree[$groupId]['fields'] = array(); + $groupTree[$groupId]['fields'] = []; } // add the fields now (note - the query row will always contain a field) - $groupTree[$groupId]['fields'][$fieldId] = array(); + $groupTree[$groupId]['fields'][$fieldId] = []; $groupTree[$groupId]['fields'][$fieldId]['id'] = $fieldId; foreach ($tableData['civicrm_custom_field'] as $v) { @@ -1123,7 +1092,7 @@ public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%% // add whereAdd for entity type self::_addWhereAdd($customGroupDAO, $entityType, $cidToken); - $groups = array(); + $groups = []; $permissionClause = CRM_Core_Permission::customGroupClause(CRM_Core_Permission::VIEW, NULL, TRUE); $customGroupDAO->whereAdd($permissionClause); @@ -1134,12 +1103,12 @@ public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%% // process each group with menu tab while ($customGroupDAO->fetch()) { - $group = array(); + $group = []; $group['id'] = $customGroupDAO->id; $group['path'] = $path; $group['title'] = "$customGroupDAO->title"; $group['query'] = "reset=1&gid={$customGroupDAO->id}&cid={$cidToken}"; - $group['extra'] = array('gid' => $customGroupDAO->id); + $group['extra'] = ['gid' => $customGroupDAO->id]; $group['table_name'] = $customGroupDAO->table_name; $group['is_multiple'] = $customGroupDAO->is_multiple; $groups[] = $group; @@ -1239,6 +1208,10 @@ public static function getAllCustomGroupsByBaseEntity($entityType) { */ private static function _addWhereAdd(&$customGroupDAO, $entityType, $entityID = NULL, $allSubtypes = FALSE) { $addSubtypeClause = FALSE; + // This function isn't really accessible with user data but since the string + // is not passed as a param to the query CRM_Core_DAO::escapeString seems like a harmless + // precaution. + $entityType = CRM_Core_DAO::escapeString($entityType); switch ($entityType) { case 'Contact': @@ -1261,13 +1234,7 @@ private static function _addWhereAdd(&$customGroupDAO, $entityType, $entityID = } break; - case 'Case': - case 'Location': - case 'Address': - case 'Activity': - case 'Contribution': - case 'Membership': - case 'Participant': + default: $customGroupDAO->whereAdd("extends IN ('$entityType')"); break; } @@ -1276,7 +1243,7 @@ private static function _addWhereAdd(&$customGroupDAO, $entityType, $entityID = $csType = is_numeric($entityID) ? CRM_Contact_BAO_Contact::getContactSubType($entityID) : FALSE; if (!empty($csType)) { - $subtypeClause = array(); + $subtypeClause = []; foreach ($csType as $subtype) { $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype . CRM_Core_DAO::VALUE_SEPARATOR; @@ -1368,9 +1335,8 @@ public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $ switch ($field['html_type']) { case 'Multi-Select': - case 'AdvMulti-Select': case 'CheckBox': - $defaults[$elementName] = array(); + $defaults[$elementName] = []; $customOption = CRM_Core_BAO_CustomOption::getCustomOption($field['id'], $inactiveNeeded); if ($viewMode) { $checkedData = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($value, 1, -1)); @@ -1488,7 +1454,6 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { //added Multi-Select option in the below if-statement if ($field['html_type'] == 'CheckBox' || $field['html_type'] == 'Radio' || - $field['html_type'] == 'AdvMulti-Select' || $field['html_type'] == 'Multi-Select' ) { $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = 'NULL'; @@ -1505,13 +1470,12 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { if (!isset($groupTree[$groupID]['fields'][$fieldId]['customValue'])) { // field exists in db so populate value from "form". - $groupTree[$groupID]['fields'][$fieldId]['customValue'] = array(); + $groupTree[$groupID]['fields'][$fieldId]['customValue'] = []; } switch ($groupTree[$groupID]['fields'][$fieldId]['html_type']) { - //added for CheckBox - + // added for CheckBox case 'CheckBox': if (!empty($v)) { $customValue = array_keys($v); @@ -1524,10 +1488,6 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { } break; - //added for Advanced Multi-Select - - case 'AdvMulti-Select': - //added for Multi-Select case 'Multi-Select': if (!empty($v)) { $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = CRM_Core_DAO::VALUE_SEPARATOR @@ -1546,12 +1506,12 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { case 'File': if ($skipFile) { - continue; + break; } - //store the file in d/b + // store the file in d/b $entityId = explode('=', $groupTree['info']['where'][0]); - $fileParams = array('upload_date' => date('YmdHis')); + $fileParams = ['upload_date' => date('YmdHis')]; if ($groupTree[$groupID]['fields'][$fieldId]['customValue']['fid']) { $fileParams['id'] = $groupTree[$groupID]['fields'][$fieldId]['customValue']['fid']; @@ -1570,11 +1530,11 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { $v['type'] ); } - $defaults = array(); - $paramsFile = array( + $defaults = []; + $paramsFile = [ 'entity_table' => $groupTree[$groupID]['table_name'], 'entity_id' => $entityId[1], - ); + ]; CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_EntityFile', $paramsFile, @@ -1603,6 +1563,8 @@ public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) { * Return inactive custom groups. * @param string $prefix * Prefix for custom grouptree assigned to template. + * + * @throws \CiviCRM_API3_Exception */ public static function buildQuickForm(&$form, &$groupTree, $inactiveNeeded = FALSE, $prefix = '') { $form->assign_by_ref("{$prefix}groupTree", $groupTree); @@ -1621,6 +1583,9 @@ public static function buildQuickForm(&$form, &$groupTree, $inactiveNeeded = FAL $fieldId = $field['id']; $elementName = $field['element_name']; CRM_Core_BAO_CustomField::addQuickFormElement($form, $elementName, $fieldId, $required); + if ($form->getAction() == CRM_Core_Action::VIEW) { + $form->getElement($elementName)->freeze(); + } } } } @@ -1634,21 +1599,22 @@ public static function buildQuickForm(&$form, &$groupTree, $inactiveNeeded = FAL * The type of custom group we are using. * * @return array + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception */ public static function extractGetParams(&$form, $type) { if (empty($_GET)) { - return array(); + return []; } $groupTree = CRM_Core_BAO_CustomGroup::getTree($type); - $customValue = array(); - $htmlType = array( + $customValue = []; + $htmlType = [ 'CheckBox', 'Multi-Select', - 'AdvMulti-Select', 'Select', 'Radio', - ); + ]; foreach ($groupTree as $group) { if (!isset($group['fields'])) { @@ -1666,13 +1632,12 @@ public static function extractGetParams(&$form, $type) { $valid = CRM_Core_BAO_CustomValue::typecheck($field['data_type'], $value); } if ($field['html_type'] == 'CheckBox' || - $field['html_type'] == 'AdvMulti-Select' || $field['html_type'] == 'Multi-Select' ) { $value = str_replace("|", ",", $value); $mulValues = explode(',', $value); $customOption = CRM_Core_BAO_CustomOption::getCustomOption($key, TRUE); - $val = array(); + $val = []; foreach ($mulValues as $v1) { foreach ($customOption as $coID => $coValue) { if (strtolower(trim($coValue['label'])) == @@ -1706,19 +1671,7 @@ public static function extractGetParams(&$form, $type) { } } elseif ($field['data_type'] == 'Date') { - if (!empty($value)) { - $time = NULL; - if (!empty($field['time_format'])) { - $time = CRM_Utils_Request::retrieve($fieldName . - '_time', 'String', $form, FALSE, NULL, 'GET'); - } - list($value, $time) = CRM_Utils_Date::setDateDefaults($value . - ' ' . $time); - if (!empty($field['time_format'])) { - $customValue[$fieldName . '_time'] = $time; - } - } - $valid = TRUE; + $valid = CRM_Utils_Rule::date($value); } if ($valid) { @@ -1814,7 +1767,7 @@ public static function mapTableName($table) { default: $query = " SELECT IF( EXISTS(SELECT name FROM civicrm_contact_type WHERE name like %1), 1, 0 )"; - $qParams = array(1 => array($table, 'String')); + $qParams = [1 => [$table, 'String']]; $result = CRM_Core_DAO::singleValueQuery($query, $qParams); if ($result) { @@ -1832,13 +1785,15 @@ public static function mapTableName($table) { /** * @param $group + * + * @throws \Exception */ public static function createTable($group) { - $params = array( + $params = [ 'name' => $group->table_name, 'is_multiple' => $group->is_multiple ? 1 : 0, 'extends_name' => self::mapTableName($group->extends), - ); + ]; $tableParams = CRM_Core_BAO_CustomField::defaultCustomTableSchema($params); @@ -1846,25 +1801,26 @@ public static function createTable($group) { } /** - * Function returns formatted groupTree, sothat form can be easily build in template + * Function returns formatted groupTree, so that form can be easily built in template * * @param array $groupTree * @param int $groupCount - * Group count by default 1, but can varry for multiple value custom data. - * @param object $form + * Group count by default 1, but can vary for multiple value custom data. + * @param \CRM_Core_Form $form * * @return array + * @throws \CRM_Core_Exception */ public static function formatGroupTree(&$groupTree, $groupCount = 1, &$form = NULL) { - $formattedGroupTree = array(); - $uploadNames = $formValues = array(); + $formattedGroupTree = []; + $uploadNames = $formValues = []; // retrieve qf key from url $qfKey = CRM_Utils_Request::retrieve('qf', 'String'); // fetch submitted custom field values later use to set as a default values if ($qfKey) { - $submittedValues = CRM_Core_BAO_Cache::getItem('custom data', $qfKey); + $submittedValues = Civi::cache('customData')->get($qfKey); } foreach ($groupTree as $key => $value) { @@ -1923,7 +1879,7 @@ public static function formatGroupTree(&$groupTree, $groupCount = 1, &$form = NU if (count($formValues)) { $qf = $form->get('qfKey'); $form->assign('qfKey', $qf); - CRM_Core_BAO_Cache::setItem($formValues, 'custom data', $qf); + Civi::cache('customData')->set($qf, $formValues); } // hack for field type File @@ -1941,7 +1897,7 @@ public static function formatGroupTree(&$groupTree, $groupCount = 1, &$form = NU /** * Build custom data view. * - * @param CRM_Core_Form $form + * @param CRM_Core_Form|CRM_Core_Page $form * Page object. * @param array $groupTree * @param bool $returnCount @@ -1952,9 +1908,10 @@ public static function formatGroupTree(&$groupTree, $groupCount = 1, &$form = NU * @param int $entityId * * @return array|int + * @throws \Exception */ public static function buildCustomDataView(&$form, &$groupTree, $returnCount = FALSE, $gID = NULL, $prefix = NULL, $customValueId = NULL, $entityId = NULL) { - $details = array(); + $details = []; foreach ($groupTree as $key => $group) { if ($key === 'info') { continue; @@ -1974,13 +1931,13 @@ public static function buildCustomDataView(&$form, &$groupTree, $returnCount = F $details[$groupID][$values['id']]['collapse_display'] = CRM_Utils_Array::value('collapse_display', $group); $details[$groupID][$values['id']]['collapse_adv_display'] = CRM_Utils_Array::value('collapse_adv_display', $group); $details[$groupID][$values['id']]['style'] = CRM_Utils_Array::value('style', $group); - $details[$groupID][$values['id']]['fields'][$k] = array( + $details[$groupID][$values['id']]['fields'][$k] = [ 'field_title' => CRM_Utils_Array::value('label', $properties), 'field_type' => CRM_Utils_Array::value('html_type', $properties), 'field_data_type' => CRM_Utils_Array::value('data_type', $properties), 'field_value' => CRM_Core_BAO_CustomField::displayValue($values['data'], $properties['id'], $entityId), 'options_per_line' => CRM_Utils_Array::value('options_per_line', $properties), - ); + ]; // editable = whether this set contains any non-read-only fields if (!isset($details[$groupID][$values['id']]['editable'])) { $details[$groupID][$values['id']]['editable'] = FALSE; @@ -2007,19 +1964,19 @@ public static function buildCustomDataView(&$form, &$groupTree, $returnCount = F $details[$groupID][0]['collapse_display'] = CRM_Utils_Array::value('collapse_display', $group); $details[$groupID][0]['collapse_adv_display'] = CRM_Utils_Array::value('collapse_adv_display', $group); $details[$groupID][0]['style'] = CRM_Utils_Array::value('style', $group); - $details[$groupID][0]['fields'][$k] = array('field_title' => CRM_Utils_Array::value('label', $properties)); + $details[$groupID][0]['fields'][$k] = ['field_title' => CRM_Utils_Array::value('label', $properties)]; } } } if ($returnCount) { - //return a single value count if group id is passed to function - //else return a groupId and count mapped array + // return a single value count if group id is passed to function + // else return a groupId and count mapped array if (!empty($gID)) { return count($details[$gID]); } else { - $countValue = array(); + $countValue = []; foreach ($details as $key => $value) { $countValue[$key] = count($details[$key]); } @@ -2046,7 +2003,7 @@ public static function getGroupTitles($fieldIds) { return NULL; } - $groupLabels = array(); + $groupLabels = []; $fIds = "(" . implode(',', $fieldIds) . ")"; $query = " @@ -2058,12 +2015,12 @@ public static function getGroupTitles($fieldIds) { $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $groupLabels[$dao->fieldID] = array( + $groupLabels[$dao->fieldID] = [ 'fieldID' => $dao->fieldID, 'fieldLabel' => $dao->fieldLabel, 'groupID' => $dao->groupID, 'groupTitle' => $dao->groupTitle, - ); + ]; } return $groupLabels; @@ -2116,13 +2073,14 @@ public static function isGroupEmpty($gID) { * * @return array * Array of types. + * @throws \Exception */ - public static function getExtendedObjectTypes(&$types = array()) { - static $flag = FALSE, $objTypes = array(); + public static function getExtendedObjectTypes(&$types = []) { + static $flag = FALSE, $objTypes = []; if (!$flag) { - $extendObjs = array(); - CRM_Core_OptionValue::getValues(array('name' => 'cg_extend_objects'), $extendObjs); + $extendObjs = []; + CRM_Core_OptionValue::getValues(['name' => 'cg_extend_objects'], $extendObjs); foreach ($extendObjs as $ovId => $ovValues) { if ($ovValues['description']) { @@ -2130,7 +2088,7 @@ public static function getExtendedObjectTypes(&$types = array()) { list($callback, $args) = explode(';', trim($ovValues['description'])); if (empty($args)) { - $args = array(); + $args = []; } if (!is_array($args)) { @@ -2138,8 +2096,7 @@ public static function getExtendedObjectTypes(&$types = array()) { } list($className) = explode('::', $callback); - require_once str_replace('_', DIRECTORY_SEPARATOR, $className) . - '.php'; + require_once str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; $objTypes[$ovValues['value']] = call_user_func_array($callback, $args); } @@ -2158,7 +2115,7 @@ public static function getExtendedObjectTypes(&$types = array()) { * @return bool */ public static function hasReachedMaxLimit($customGroupId, $entityId) { - //check whether the group is multiple + // check whether the group is multiple $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'is_multiple'); $isMultiple = ($isMultiple) ? TRUE : FALSE; $hasReachedMax = FALSE; @@ -2170,9 +2127,9 @@ public static function hasReachedMaxLimit($customGroupId, $entityId) { } else { $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'table_name'); - //count the number of entries for a entity + // count the number of entries for a entity $sql = "SELECT COUNT(id) FROM {$tableName} WHERE entity_id = %1"; - $params = array(1 => array($entityId, 'Integer')); + $params = [1 => [$entityId, 'Integer']]; $count = CRM_Core_DAO::singleValueQuery($sql, $params); if ($count >= $maxMultiple) { @@ -2187,7 +2144,7 @@ public static function hasReachedMaxLimit($customGroupId, $entityId) { * @return array */ public static function getMultipleFieldGroup() { - $multipleGroup = array(); + $multipleGroup = []; $dao = new CRM_Core_DAO_CustomGroup(); $dao->is_multiple = 1; $dao->is_active = 1; @@ -2198,4 +2155,82 @@ public static function getMultipleFieldGroup() { return $multipleGroup; } + /** + * Build the metadata tree for the custom group. + * + * @param string $entityType + * @param array $toReturn + * @param array $subTypes + * @param string $queryString + * @param array $params + * @param string $subType + * + * @return array + * @throws \CRM_Core_Exception + */ + private static function buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType) { + $groupTree = $multipleFieldGroups = []; + $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params); + $customValueTables = []; + + // process records + while ($crmDAO->fetch()) { + // get the id's + $groupID = $crmDAO->civicrm_custom_group_id; + $fieldId = $crmDAO->civicrm_custom_field_id; + if ($crmDAO->civicrm_custom_group_is_multiple) { + $multipleFieldGroups[$groupID] = $crmDAO->civicrm_custom_group_table_name; + } + // create an array for groups if it does not exist + if (!array_key_exists($groupID, $groupTree)) { + $groupTree[$groupID] = []; + $groupTree[$groupID]['id'] = $groupID; + + // populate the group information + foreach ($toReturn['custom_group'] as $fieldName) { + $fullFieldName = "civicrm_custom_group_$fieldName"; + if ($fieldName == 'id' || + is_null($crmDAO->$fullFieldName) + ) { + continue; + } + // CRM-5507 + // This is an old bit of code - per the CRM number & probably does not work reliably if + // that one contact sub-type exists. + if ($fieldName == 'extends_entity_column_value' && !empty($subTypes[0])) { + $groupTree[$groupID]['subtype'] = self::validateSubTypeByEntity($entityType, $subType); + } + $groupTree[$groupID][$fieldName] = $crmDAO->$fullFieldName; + } + $groupTree[$groupID]['fields'] = []; + + $customValueTables[$crmDAO->civicrm_custom_group_table_name] = []; + } + + // add the fields now (note - the query row will always contain a field) + // we only reset this once, since multiple values come is as multiple rows + if (!array_key_exists($fieldId, $groupTree[$groupID]['fields'])) { + $groupTree[$groupID]['fields'][$fieldId] = []; + } + + $customValueTables[$crmDAO->civicrm_custom_group_table_name][$crmDAO->civicrm_custom_field_column_name] = 1; + $groupTree[$groupID]['fields'][$fieldId]['id'] = $fieldId; + // populate information for a custom field + foreach ($toReturn['custom_field'] as $fieldName) { + $fullFieldName = "civicrm_custom_field_$fieldName"; + if ($fieldName == 'id' || + is_null($crmDAO->$fullFieldName) + ) { + continue; + } + $groupTree[$groupID]['fields'][$fieldId][$fieldName] = $crmDAO->$fullFieldName; + } + } + + if (!empty($customValueTables)) { + $groupTree['info'] = ['tables' => $customValueTables]; + } + return [$multipleFieldGroups, $groupTree]; + } + } diff --git a/CRM/Core/BAO/CustomOption.php b/CRM/Core/BAO/CustomOption.php index efbb6518c4e6..0eb77b2cfff9 100644 --- a/CRM/Core/BAO/CustomOption.php +++ b/CRM/Core/BAO/CustomOption.php @@ -1,9 +1,9 @@ $label) { - $options[] = array( + $options[] = [ 'label' => $label, 'value' => $value, - ); + ]; } return $options; @@ -102,8 +102,8 @@ public static function getCustomOption( * -rp = rowcount * -page= offset */ - static public function getOptionListSelector(&$params) { - $options = array(); + public static function getOptionListSelector(&$params) { + $options = []; $field = CRM_Core_BAO_CustomField::getFieldObject($params['fid']); $defVal = CRM_Utils_Array::explodePadded($field->default_value); @@ -115,7 +115,7 @@ static public function getOptionListSelector(&$params) { if (!$field->option_group_id) { return $options; } - $queryParams = array(1 => array($field->option_group_id, 'Integer')); + $queryParams = [1 => [$field->option_group_id, 'Integer']]; $total = "SELECT COUNT(*) FROM civicrm_option_value WHERE option_group_id = %1"; $params['total'] = CRM_Core_DAO::singleValueQuery($total, $queryParams); @@ -126,10 +126,10 @@ static public function getOptionListSelector(&$params) { $dao = CRM_Core_DAO::executeQuery($query, $queryParams); $links = CRM_Custom_Page_Option::actionLinks(); - $fields = array('id', 'label', 'value'); + $fields = ['id', 'label', 'value']; $config = CRM_Core_Config::singleton(); while ($dao->fetch()) { - $options[$dao->id] = array(); + $options[$dao->id] = []; foreach ($fields as $k) { $options[$dao->id][$k] = $dao->$k; } @@ -143,7 +143,19 @@ static public function getOptionListSelector(&$params) { $class .= ' disabled'; $action -= CRM_Core_Action::DISABLE; } - if (in_array($field->html_type, array('CheckBox', 'AdvMulti-Select', 'Multi-Select'))) { + + $isGroupLocked = (bool) CRM_Core_DAO::getFieldValue( + CRM_Core_DAO_OptionGroup::class, + $field->option_group_id, + 'is_locked' + ); + + // disable deletion of option values for locked option groups + if (($action & CRM_Core_Action::DELETE) && $isGroupLocked) { + $action -= CRM_Core_Action::DELETE; + } + + if (in_array($field->html_type, ['CheckBox', 'Multi-Select'])) { if (isset($defVal) && in_array($dao->value, $defVal)) { $options[$dao->id]['is_default'] = ''; } @@ -159,16 +171,16 @@ static public function getOptionListSelector(&$params) { $options[$dao->id]['is_default'] = ''; } } - + $options[$dao->id]['description'] = $dao->description; $options[$dao->id]['class'] = $dao->id . ',' . $class; $options[$dao->id]['is_active'] = empty($dao->is_active) ? ts('No') : ts('Yes'); $options[$dao->id]['links'] = CRM_Core_Action::formLink($links, $action, - array( + [ 'id' => $dao->id, 'fid' => $params['fid'], 'gid' => $params['gid'], - ), + ], ts('more'), FALSE, 'customOption.row.actions', @@ -197,22 +209,22 @@ public static function del($optionId) { WHERE v.id = %1 AND g.id = f.option_group_id AND g.id = v.option_group_id"; - $params = array(1 => array($optionId, 'Integer')); + $params = [1 => [$optionId, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); if ($dao->fetch()) { if (in_array($dao->dataType, - array('Int', 'Float', 'Money', 'Boolean') + ['Int', 'Float', 'Money', 'Boolean'] )) { $value = 0; } else { $value = ''; } - $params = array( + $params = [ 'optionId' => $optionId, 'fieldId' => $dao->id, 'value' => $value, - ); + ]; // delete this value from the tables self::updateCustomValues($params); @@ -221,7 +233,7 @@ public static function del($optionId) { DELETE FROM civicrm_option_value WHERE id = %1"; - $params = array(1 => array($optionId, 'Integer')); + $params = [1 => [$optionId, 'Integer']]; CRM_Core_DAO::executeQuery($query, $params); } } @@ -247,7 +259,7 @@ public static function updateCustomValues($params) { civicrm_custom_field f WHERE f.custom_group_id = g.id AND f.id = %1"; - $queryParams = array(1 => array($params['fieldId'], 'Integer')); + $queryParams = [1 => [$params['fieldId'], 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $queryParams); if ($dao->fetch()) { if ($dao->dataType == 'Money') { @@ -267,19 +279,18 @@ public static function updateCustomValues($params) { else { $dataType = $dao->dataType; } - $queryParams = array( - 1 => array( + $queryParams = [ + 1 => [ $params['value'], $dataType, - ), - 2 => array( + ], + 2 => [ $params['optionId'], 'Integer', - ), - ); + ], + ]; break; - case 'AdvMulti-Select': case 'Multi-Select': case 'CheckBox': $oldString = CRM_Core_DAO::VALUE_SEPARATOR . $oldValue . CRM_Core_DAO::VALUE_SEPARATOR; @@ -287,10 +298,10 @@ public static function updateCustomValues($params) { $query = " UPDATE {$dao->tableName} SET {$dao->columnName} = REPLACE( {$dao->columnName}, %1, %2 )"; - $queryParams = array( - 1 => array($oldString, 'String'), - 2 => array($newString, 'String'), - ); + $queryParams = [ + 1 => [$oldString, 'String'], + 2 => [$newString, 'String'], + ]; break; default: @@ -323,24 +334,22 @@ public static function updateValue($optionId, $newValue) { $customGroup->id = $customField->custom_group_id; $customGroup->find(TRUE); if (CRM_Core_BAO_CustomField::isSerialized($customField)) { - $params = array( - 1 => array(CRM_Utils_Array::implodePadded($oldValue), 'String'), - 2 => array(CRM_Utils_Array::implodePadded($newValue), 'String'), - 3 => array('%' . CRM_Utils_Array::implodePadded($oldValue) . '%', 'String'), - ); + $params = [ + 1 => [CRM_Utils_Array::implodePadded($oldValue), 'String'], + 2 => [CRM_Utils_Array::implodePadded($newValue), 'String'], + 3 => ['%' . CRM_Utils_Array::implodePadded($oldValue) . '%', 'String'], + ]; } else { - $params = array( - 1 => array($oldValue, 'String'), - 2 => array($newValue, 'String'), - 3 => array($oldValue, 'String'), - ); + $params = [ + 1 => [$oldValue, 'String'], + 2 => [$newValue, 'String'], + 3 => [$oldValue, 'String'], + ]; } $sql = "UPDATE `{$customGroup->table_name}` SET `{$customField->column_name}` = REPLACE(`{$customField->column_name}`, %1, %2) WHERE `{$customField->column_name}` LIKE %3"; - $customGroup->free(); CRM_Core_DAO::executeQuery($sql, $params); } - $customField->free(); } } diff --git a/CRM/Core/BAO/CustomQuery.php b/CRM/Core/BAO/CustomQuery.php index 81e5eb876f7a..a3e2cf273677 100644 --- a/CRM/Core/BAO/CustomQuery.php +++ b/CRM/Core/BAO/CustomQuery.php @@ -1,9 +1,9 @@ _fields; + } + /** * Searching for contacts? * - * @var boolean + * @var bool */ protected $_contactSearch; @@ -107,7 +114,7 @@ class CRM_Core_BAO_CustomQuery { * * @var array */ - static $extendsMap = array( + public static $extendsMap = [ 'Contact' => 'civicrm_contact', 'Individual' => 'civicrm_contact', 'Household' => 'civicrm_contact', @@ -126,7 +133,7 @@ class CRM_Core_BAO_CustomQuery { 'Address' => 'civicrm_address', 'Campaign' => 'civicrm_campaign', 'Survey' => 'civicrm_survey', - ); + ]; /** * Class constructor. @@ -140,20 +147,20 @@ class CRM_Core_BAO_CustomQuery { * @param bool $contactSearch * @param array $locationSpecificFields */ - public function __construct($ids, $contactSearch = FALSE, $locationSpecificFields = array()) { + public function __construct($ids, $contactSearch = FALSE, $locationSpecificFields = []) { $this->_ids = &$ids; $this->_locationSpecificCustomFields = $locationSpecificFields; - $this->_select = array(); - $this->_element = array(); - $this->_tables = array(); - $this->_whereTables = array(); - $this->_where = array(); - $this->_qill = array(); - $this->_options = array(); + $this->_select = []; + $this->_element = []; + $this->_tables = []; + $this->_whereTables = []; + $this->_where = []; + $this->_qill = []; + $this->_options = []; - $this->_fields = array(); $this->_contactSearch = $contactSearch; + $this->_fields = CRM_Core_BAO_CustomField::getFields('ANY', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE); if (empty($this->_ids)) { return; @@ -177,37 +184,16 @@ public function __construct($ids, $contactSearch = FALSE, $locationSpecificField $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - // get the group dao to figure which class this custom field extends - $extends = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $dao->custom_group_id, 'extends'); - if (array_key_exists($extends, self::$extendsMap)) { - $extendsTable = self::$extendsMap[$extends]; - } - elseif (in_array($extends, CRM_Contact_BAO_ContactType::subTypes())) { - // if $extends is a subtype, refer contact table - $extendsTable = self::$extendsMap['Contact']; - } - $this->_fields[$dao->id] = array( - 'id' => $dao->id, - 'label' => $dao->label, - 'extends' => $extendsTable, - 'data_type' => $dao->data_type, - 'html_type' => $dao->html_type, - 'is_search_range' => $dao->is_search_range, - 'column_name' => $dao->column_name, - 'table_name' => $dao->table_name, - 'option_group_id' => $dao->option_group_id, - ); - // Deprecated (and poorly named) cache of field attributes - $this->_options[$dao->id] = array( - 'attributes' => array( + $this->_options[$dao->id] = [ + 'attributes' => [ 'label' => $dao->label, 'data_type' => $dao->data_type, 'html_type' => $dao->html_type, - ), - ); + ], + ]; - $options = CRM_Core_PseudoConstant::get('CRM_Core_BAO_CustomField', 'custom_' . $dao->id, array(), 'search'); + $options = CRM_Core_PseudoConstant::get('CRM_Core_BAO_CustomField', 'custom_' . $dao->id, [], 'search'); if ($options) { $this->_options[$dao->id] += $options; } @@ -227,36 +213,21 @@ public function select() { return; } - foreach ($this->_fields as $id => $field) { + foreach (array_keys($this->_ids) as $id) { + $field = $this->_fields[$id]; $name = $field['table_name']; $fieldName = 'custom_' . $field['id']; $this->_select["{$name}_id"] = "{$name}.id as {$name}_id"; $this->_element["{$name}_id"] = 1; $this->_select[$fieldName] = "{$field['table_name']}.{$field['column_name']} as $fieldName"; $this->_element[$fieldName] = 1; - $joinTable = NULL; + $joinTable = $field['search_table']; // CRM-14265 - if ($field['extends'] == 'civicrm_group') { + if ($joinTable == 'civicrm_group' || empty($joinTable)) { return; } - elseif ($field['extends'] == 'civicrm_contact') { - $joinTable = 'contact_a'; - } - elseif ($field['extends'] == 'civicrm_contribution') { - $joinTable = $field['extends']; - } - elseif (in_array($field['extends'], self::$extendsMap)) { - $joinTable = $field['extends']; - } - else { - return; - } - - $this->_tables[$name] = "\nLEFT JOIN $name ON $name.entity_id = $joinTable.id"; - if ($this->_ids[$id]) { - $this->_whereTables[$name] = $this->_tables[$name]; - } + $this->joinCustomTableForField($field); if ($joinTable) { $joinClause = 1; @@ -268,7 +239,7 @@ public function select() { $joinClause = "\nLEFT JOIN $joinTable `$locationType-address` ON (`$locationType-address`.contact_id = contact_a.id AND `$locationType-address`.location_type_id = $locationTypeId)"; } $this->_tables[$name] = "\nLEFT JOIN $name ON $name.entity_id = `$joinTableAlias`.id"; - if ($this->_ids[$id]) { + if (!empty($this->_ids[$id])) { $this->_whereTables[$name] = $this->_tables[$name]; } if ($joinTable != 'contact_a') { @@ -294,8 +265,6 @@ public function where() { continue; } - $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower'; - foreach ($values as $tuple) { list($name, $op, $value, $grouping, $wildcard) = $tuple; @@ -322,6 +291,8 @@ public function where() { $qillOp = CRM_Utils_Array::value($op, CRM_Core_SelectValues::getSearchBuilderOperators(), $op); + // Ensure the table is joined in (eg if in where but not select). + $this->joinCustomTableForField($field); switch ($field['data_type']) { case 'String': case 'StateProvince': @@ -334,12 +305,12 @@ public function where() { // fix $value here to escape sql injection attacks if (!is_array($value)) { if ($field['data_type'] == 'String') { - $value = CRM_Utils_Type::escape($strtolower($value), 'String'); + $value = CRM_Utils_Type::escape($value, 'String'); } elseif ($value) { $value = CRM_Utils_Type::escape($value, 'Integer'); } - $value = str_replace(array('[', ']', ','), array('\[', '\]', '[:comma:]'), $value); + $value = str_replace(['[', ']', ','], ['\[', '\]', '[:comma:]'], $value); $value = str_replace('|', '[:separator:]', $value); } elseif ($isSerialized) { @@ -350,8 +321,14 @@ public function where() { // CRM-19006: escape characters like comma, | before building regex pattern $value = (array) $value; foreach ($value as $key => $val) { - $value[$key] = str_replace(array('[', ']', ','), array('\[', '\]', '[:comma:]'), $val); + $value[$key] = str_replace(['[', ']', ','], ['\[', '\]', '[:comma:]'], $val); $value[$key] = str_replace('|', '[:separator:]', $value[$key]); + if ($field['data_type'] == 'String') { + $value[$key] = CRM_Utils_Type::escape($value[$key], 'String'); + } + elseif ($value) { + $value[$key] = CRM_Utils_Type::escape($value[$key], 'Integer'); + } } $value = implode(',', $value); } @@ -360,7 +337,7 @@ public function where() { if ($isSerialized && !CRM_Utils_System::isNull($value) && !strstr($op, 'NULL') && !strstr($op, 'LIKE')) { $sp = CRM_Core_DAO::VALUE_SEPARATOR; $value = str_replace(",", "$sp|$sp", $value); - $value = str_replace(array('[:comma:]', '(', ')'), array(',', '[[.left-parenthesis.]]', '[[.right-parenthesis.]]'), $value); + $value = str_replace(['[:comma:]', '(', ')'], [',', '[(]', '[)]'], $value); $op = (strstr($op, '!') || strstr($op, 'NOT')) ? 'NOT RLIKE' : 'RLIKE'; $value = $sp . $value . $sp; @@ -391,7 +368,7 @@ public function where() { case 'Int': $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Integer'); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['label'], 2 => $qillOp, 3 => $qillValue));; + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['label'], 2 => $qillOp, 3 => $qillValue]);; break; case 'Boolean': @@ -406,35 +383,44 @@ public function where() { $qillValue = $value ? 'Yes' : 'No'; } $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Integer'); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['label'], 2 => $qillOp, 3 => $qillValue)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['label'], 2 => $qillOp, 3 => $qillValue]); break; case 'Link': case 'Memo': $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'String'); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['label'], 2 => $qillOp, 3 => $qillValue)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['label'], 2 => $qillOp, 3 => $qillValue]); break; case 'Money': $value = CRM_Utils_Array::value($op, (array) $value, $value); if (is_array($value)) { foreach ($value as $key => $val) { - $value[$key] = CRM_Utils_Rule::cleanMoney($value[$key]); + // @todo - this clean money should be in the form layer - it's highly likely to be doing more harm than good here + // Note the only place I can find that this code is reached by is searching a custom money field in advanced search. + // with euro style comma separators this doesn't work - with or without this cleanMoney. + // So this should be removed but is not increasing the brokeness IMHO + $value[$op][$key] = CRM_Utils_Rule::cleanMoney($value[$key]); } } else { + // @todo - this clean money should be in the form layer - it's highly likely to be doing more harm than good here + // comments per above apply. cleanMoney $value = CRM_Utils_Rule::cleanMoney($value); } case 'Float': $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Float'); - $this->_qill[$grouping][] = ts("%1 %2 %3", array(1 => $field['label'], 2 => $qillOp, 3 => $qillValue)); + $this->_qill[$grouping][] = ts("%1 %2 %3", [1 => $field['label'], 2 => $qillOp, 3 => $qillValue]); break; case 'Date': - $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'String'); - list($qillOp, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $field['label'], $value, $op, array(), CRM_Utils_Type::T_DATE); - $this->_qill[$grouping][] = "{$field['label']} $qillOp '$qillVal'"; + if (substr($name, -9, 9) !== '_relative') { + // Relative dates are handled in the buildRelativeDateQuery function. + $this->_where[$grouping][] = CRM_Contact_BAO_Query::buildClause($fieldName, $op, $value, 'Date'); + list($qillOp, $qillVal) = CRM_Contact_BAO_Query::buildQillForFieldValue(NULL, $field['label'], $value, $op, [], CRM_Utils_Type::T_DATE); + $this->_qill[$grouping][] = "{$field['label']} $qillOp '$qillVal'"; + } break; case 'File': @@ -471,7 +457,7 @@ public function query() { $whereStr = NULL; if (!empty($this->_where)) { - $clauses = array(); + $clauses = []; foreach ($this->_where as $grouping => $values) { if (!empty($values)) { $clauses[] = ' ( ' . implode(' AND ', $values) . ' ) '; @@ -482,11 +468,23 @@ public function query() { } } - return array( + return [ implode(' , ', $this->_select), implode(' ', $this->_tables), $whereStr, - ); + ]; + } + + /** + * Join the custom table for the field in (if not already in the query). + * + * @param array $field + */ + protected function joinCustomTableForField($field) { + $name = $field['table_name']; + $join = "\nLEFT JOIN $name ON $name.entity_id = {$field['search_table']}.id"; + $this->_tables[$name] = $this->_tables[$name] ?? $join; + $this->_whereTables[$name] = $this->_whereTables[$name] ?? $join; } } diff --git a/CRM/Core/BAO/CustomValue.php b/CRM/Core/BAO/CustomValue.php index 0134869a4840..7df55cc393e1 100644 --- a/CRM/Core/BAO/CustomValue.php +++ b/CRM/Core/BAO/CustomValue.php @@ -1,9 +1,9 @@ $formValues[$key]); + $formValues[$key] = ['IN' => $formValues[$key]]; } } elseif (($htmlType == 'TextArea' || ($htmlType == 'Text' && $dataType == 'String') ) && strstr($formValues[$key], '%') ) { - $formValues[$key] = array('LIKE' => $formValues[$key]); + $formValues[$key] = ['LIKE' => $formValues[$key]]; } } } @@ -207,13 +206,18 @@ public static function deleteCustomValue($customValueID, $customGroupID) { // first we need to find custom value table, from custom group ID $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupID, 'table_name'); + // Retrieve the $entityId so we can pass that to the hook. + $entityID = CRM_Core_DAO::singleValueQuery("SELECT entity_id FROM {$tableName} WHERE id = %1", [ + 1 => [$customValueID, 'Integer'], + ]); + // delete custom value from corresponding custom value table $sql = "DELETE FROM {$tableName} WHERE id = {$customValueID}"; CRM_Core_DAO::executeQuery($sql); CRM_Utils_Hook::custom('delete', $customGroupID, - NULL, + $entityID, $customValueID ); } diff --git a/CRM/Core/BAO/CustomValueTable.php b/CRM/Core/BAO/CustomValueTable.php index 9d8245380c59..c25baac556bd 100644 --- a/CRM/Core/BAO/CustomValueTable.php +++ b/CRM/Core/BAO/CustomValueTable.php @@ -1,9 +1,9 @@ $tables) { foreach ($tables as $index => $fields) { $sqlOP = NULL; @@ -53,8 +60,8 @@ public static function create(&$customParams) { $hookOP = NULL; $entityID = NULL; $isMultiple = FALSE; - $set = array(); - $params = array(); + $set = []; + $params = []; $count = 1; foreach ($fields as $field) { if (!$sqlOP) { @@ -64,7 +71,7 @@ public static function create(&$customParams) { if (array_key_exists('id', $field)) { $sqlOP = "UPDATE $tableName "; $where = " WHERE id = %{$count}"; - $params[$count] = array($field['id'], 'Integer'); + $params[$count] = [$field['id'], 'Integer']; $count++; $hookOP = 'edit'; } @@ -88,9 +95,9 @@ public static function create(&$customParams) { elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) { //fix for multi select state, CRM-3437 $mulValues = explode(',', $value); - $validStates = array(); + $validStates = []; foreach ($mulValues as $key => $stateVal) { - $states = array(); + $states = []; $states['state_province'] = trim($stateVal); CRM_Utils_Array::lookupValue($states, 'state_province', @@ -122,7 +129,6 @@ public static function create(&$customParams) { case 'Country': $type = 'Integer'; - $mulValues = explode(',', $value); if (is_array($value)) { $value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR; $type = 'String'; @@ -130,9 +136,9 @@ public static function create(&$customParams) { elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) { //fix for multi select country, CRM-3437 $mulValues = explode(',', $value); - $validCountries = array(); + $validCountries = []; foreach ($mulValues as $key => $countryVal) { - $countries = array(); + $countries = []; $countries['country'] = trim($countryVal); CRM_Utils_Array::lookupValue($countries, 'country', CRM_Core_PseudoConstant::country(), TRUE @@ -175,7 +181,6 @@ public static function create(&$customParams) { $entityFileDAO->entity_id = $field['entity_id']; $entityFileDAO->file_id = $field['file_id']; $entityFileDAO->save(); - $entityFileDAO->free(); $value = $field['file_id']; $type = 'String'; break; @@ -217,20 +222,31 @@ public static function create(&$customParams) { default: break; } - if (strtolower($value) === "null") { + if ($value === 'null') { // when unsetting a value to null, we don't need to validate the type // https://projectllr.atlassian.net/browse/VGQBMP-20 $set[$field['column_name']] = $value; } else { $set[$field['column_name']] = "%{$count}"; - $params[$count] = array($value, $type); + $params[$count] = [$value, $type]; $count++; } + + $fieldExtends = CRM_Utils_Array::value('extends', $field); + if ( + CRM_Utils_Array::value('entity_table', $field) == 'civicrm_contact' + || $fieldExtends == 'Contact' + || $fieldExtends == 'Individual' + || $fieldExtends == 'Organization' + || $fieldExtends == 'Household' + ) { + $paramFieldsExtendContactForEntities[$entityID]['custom_' . CRM_Utils_Array::value('custom_field_id', $field)] = CRM_Utils_Array::value('custom_field_id', $field); + } } if (!empty($set)) { - $setClause = array(); + $setClause = []; foreach ($set as $n => $v) { $setClause[] = "$n = $v"; } @@ -238,14 +254,14 @@ public static function create(&$customParams) { if (!$where) { // do this only for insert $set['entity_id'] = "%{$count}"; - $params[$count] = array($entityID, 'Integer'); + $params[$count] = [$entityID, 'Integer']; $count++; $fieldNames = implode(',', CRM_Utils_Type::escapeAll(array_keys($set), 'MysqlColumnNameOrAlias')); $fieldValues = implode(',', array_values($set)); $query = "$sqlOP ( $fieldNames ) VALUES ( $fieldValues )"; // for multiple values we dont do on duplicate key update - if (!$isMultiple) { + if (!$isMultiple && $parentOperation !== 'create') { $query .= " ON DUPLICATE KEY UPDATE $setClause"; } } @@ -262,6 +278,10 @@ public static function create(&$customParams) { } } } + + if (!empty($paramFieldsExtendContactForEntities)) { + CRM_Contact_BAO_Contact::updateGreetingsOnTokenFieldChange($paramFieldsExtendContactForEntities, ['contact_id' => $entityID]); + } } /** @@ -322,12 +342,17 @@ public static function fieldToSQLType($type, $maxLength = 255) { * @param array $params * @param $entityTable * @param int $entityID + * @param string $parentOperation Operation being taken on the parent entity. + * If we know the parent entity is doing an insert we can skip the + * ON DUPLICATE UPDATE - which improves performance and reduces deadlocks. + * - edit + * - create */ - public static function store(&$params, $entityTable, $entityID) { - $cvParams = array(); + public static function store($params, $entityTable, $entityID, $parentOperation = NULL) { + $cvParams = []; foreach ($params as $fieldID => $param) { foreach ($param as $index => $customValue) { - $cvParam = array( + $cvParam = [ 'entity_table' => $entityTable, 'entity_id' => $entityID, 'value' => $customValue['value'], @@ -338,7 +363,7 @@ public static function store(&$params, $entityTable, $entityID) { 'column_name' => $customValue['column_name'], 'is_multiple' => CRM_Utils_Array::value('is_multiple', $customValue), 'file_id' => $customValue['file_id'], - ); + ]; // Fix Date type to be timestamp, since that is how we store in db. if ($cvParam['type'] == 'Date') { @@ -349,18 +374,18 @@ public static function store(&$params, $entityTable, $entityID) { $cvParam['id'] = $customValue['id']; } if (!array_key_exists($customValue['table_name'], $cvParams)) { - $cvParams[$customValue['table_name']] = array(); + $cvParams[$customValue['table_name']] = []; } if (!array_key_exists($index, $cvParams[$customValue['table_name']])) { - $cvParams[$customValue['table_name']][$index] = array(); + $cvParams[$customValue['table_name']][$index] = []; } $cvParams[$customValue['table_name']][$index][] = $cvParam; } } if (!empty($cvParams)) { - self::create($cvParams); + self::create($cvParams, $parentOperation); } } @@ -415,7 +440,7 @@ public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs return NULL; } - $cond = array(); + $cond = []; if ($entityType) { $cond[] = "cg.extends IN ( '$entityType' )"; } @@ -455,12 +480,12 @@ public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs "; $dao = CRM_Core_DAO::executeQuery($query); - $select = $fields = $isMultiple = array(); + $select = $fields = $isMultiple = []; while ($dao->fetch()) { if (!array_key_exists($dao->table_name, $select)) { - $fields[$dao->table_name] = array(); - $select[$dao->table_name] = array(); + $fields[$dao->table_name] = []; + $select[$dao->table_name] = []; } $fields[$dao->table_name][] = $dao->fieldID; $select[$dao->table_name][] = "{$dao->column_name} AS custom_{$dao->fieldID}"; @@ -468,7 +493,7 @@ public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs $file[$dao->table_name][$dao->fieldID] = $dao->fieldDataType; } - $result = $sortedResult = array(); + $result = $sortedResult = []; foreach ($select as $tableName => $clauses) { if (!empty($DTparams['sort'])) { $query = CRM_Core_DAO::executeQuery("SELECT id FROM {$tableName} WHERE entity_id = {$entityID}"); @@ -520,36 +545,37 @@ public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs * @return array */ public static function setValues(&$params) { + // For legacy reasons, accept this param in either format + if (empty($params['entityID']) && !empty($params['entity_id'])) { + $params['entityID'] = $params['entity_id']; + } - if (!isset($params['entityID']) || - CRM_Utils_Type::escape($params['entityID'], 'Integer', FALSE) === NULL - ) { - return CRM_Core_Error::createAPIError(ts('entityID needs to be set and of type Integer')); + if (!isset($params['entityID']) || !CRM_Utils_Type::validate($params['entityID'], 'Integer', FALSE)) { + return CRM_Core_Error::createAPIError(ts('entity_id needs to be set and of type Integer')); } // first collect all the id/value pairs. The format is: // custom_X => value or custom_X_VALUEID => value (for multiple values), VALUEID == -1, -2 etc for new insertions - $values = array(); - $fieldValues = array(); + $fieldValues = []; foreach ($params as $n => $v) { if ($customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($n, TRUE)) { $fieldID = (int ) $customFieldInfo[0]; if (CRM_Utils_Type::escape($fieldID, 'Integer', FALSE) === NULL) { return CRM_Core_Error::createAPIError(ts('field ID needs to be of type Integer for index %1', - array(1 => $fieldID) + [1 => $fieldID] )); } if (!array_key_exists($fieldID, $fieldValues)) { - $fieldValues[$fieldID] = array(); + $fieldValues[$fieldID] = []; } $id = -1; if ($customFieldInfo[1]) { $id = (int ) $customFieldInfo[1]; } - $fieldValues[$fieldID][] = array( + $fieldValues[$fieldID][] = [ 'value' => $v, 'id' => $id, - ); + ]; } } @@ -560,6 +586,7 @@ public static function setValues(&$params) { SELECT cg.table_name as table_name , cg.id as cg_id , cg.is_multiple as is_multiple, + cg.extends as extends, cf.column_name as column_name, cf.id as cf_id , cf.data_type as data_type @@ -570,7 +597,7 @@ public static function setValues(&$params) { "; $dao = CRM_Core_DAO::executeQuery($sql); - $cvParams = array(); + $cvParams = []; while ($dao->fetch()) { $dataType = $dao->data_type == 'Date' ? 'Timestamp' : $dao->data_type; @@ -600,14 +627,14 @@ public static function setValues(&$params) { // Ensure that value is of the right data type elseif (CRM_Utils_Type::escape($fieldValue['value'], $dataType, FALSE) === NULL) { return CRM_Core_Error::createAPIError(ts('value: %1 is not of the right field data type: %2', - array( + [ 1 => $fieldValue['value'], 2 => $dao->data_type, - ) + ] )); } - $cvParam = array( + $cvParam = [ 'entity_id' => $params['entityID'], 'value' => $fieldValue['value'], 'type' => $dataType, @@ -616,18 +643,23 @@ public static function setValues(&$params) { 'table_name' => $dao->table_name, 'column_name' => $dao->column_name, 'is_multiple' => $dao->is_multiple, - ); + 'extends' => $dao->extends, + ]; + + if (!empty($params['id'])) { + $cvParam['id'] = $params['id']; + } if ($cvParam['type'] == 'File') { $cvParam['file_id'] = $fieldValue['value']; } if (!array_key_exists($dao->table_name, $cvParams)) { - $cvParams[$dao->table_name] = array(); + $cvParams[$dao->table_name] = []; } if (!array_key_exists($fieldValue['id'], $cvParams[$dao->table_name])) { - $cvParams[$dao->table_name][$fieldValue['id']] = array(); + $cvParams[$dao->table_name][$fieldValue['id']] = []; } if ($fieldValue['id'] > 0) { @@ -639,7 +671,7 @@ public static function setValues(&$params) { if (!empty($cvParams)) { self::create($cvParams); - return array('is_error' => 0, 'result' => 1); + return ['is_error' => 0, 'result' => 1]; } return CRM_Core_Error::createAPIError(ts('Unknown error')); @@ -677,21 +709,21 @@ public static function &getValues(&$params) { // first collect all the ids. The format is: // custom_ID - $fieldIDs = array(); + $fieldIDs = []; foreach ($params as $n => $v) { $key = $idx = NULL; if (substr($n, 0, 7) == 'custom_') { $idx = substr($n, 7); if (CRM_Utils_Type::escape($idx, 'Integer', FALSE) === NULL) { return CRM_Core_Error::createAPIError(ts('field ID needs to be of type Integer for index %1', - array(1 => $idx) + [1 => $idx] )); } $fieldIDs[] = (int ) $idx; } } - $default = array('Contact', 'Individual', 'Household', 'Organization'); + $default = ['Contact', 'Individual', 'Household', 'Organization']; if (!($type = CRM_Utils_Array::value('entityType', $params)) || in_array($params['entityType'], $default) ) { @@ -720,17 +752,17 @@ public static function &getValues(&$params) { // note that this behaviour is undesirable from an API point of view - it should return an empty array // since this is also called by the merger code & not sure the consequences of changing // are just handling undoing this in the api layer. ie. converting the error back into a success - $result = array( + $result = [ 'is_error' => 1, 'error_message' => 'No values found for the specified entity ID and custom field(s).', - ); + ]; return $result; } else { - $result = array( + $result = [ 'is_error' => 0, 'entityID' => $params['entityID'], - ); + ]; foreach ($values as $id => $value) { $result["custom_{$id}"] = $value; } diff --git a/CRM/Core/BAO/Dashboard.php b/CRM/Core/BAO/Dashboard.php index 5b2cbffe993b..a94154d178f6 100644 --- a/CRM/Core/BAO/Dashboard.php +++ b/CRM/Core/BAO/Dashboard.php @@ -1,9 +1,9 @@ id] = $values; } @@ -101,15 +102,16 @@ public static function getDashlets($all = TRUE, $checkPermission = TRUE) { */ public static function getContactDashlets($contactID = NULL) { $contactID = $contactID ? $contactID : CRM_Core_Session::getLoggedInContactID(); - $dashlets = array(); + $dashlets = []; // Get contact dashboard dashlets. - $results = civicrm_api3('DashboardContact', 'get', array( + $results = civicrm_api3('DashboardContact', 'get', [ 'contact_id' => $contactID, 'is_active' => 1, 'dashboard_id.is_active' => 1, - 'options' => array('sort' => 'weight', 'limit' => 0), - 'return' => array( + 'dashboard_id.domain_id' => CRM_Core_Config::domainID(), + 'options' => ['sort' => 'weight', 'limit' => 0], + 'return' => [ 'id', 'weight', 'column_no', @@ -121,12 +123,12 @@ public static function getContactDashlets($contactID = NULL) { 'dashboard_id.cache_minutes', 'dashboard_id.permission', 'dashboard_id.permission_operator', - ), - )); + ], + ]); foreach ($results['values'] as $item) { if (self::checkPermission(CRM_Utils_Array::value('dashboard_id.permission', $item), CRM_Utils_Array::value('dashboard_id.permission_operator', $item))) { - $dashlets[$item['id']] = array( + $dashlets[$item['id']] = [ 'dashboard_id' => $item['dashboard_id'], 'weight' => $item['weight'], 'column_no' => $item['column_no'], @@ -135,14 +137,14 @@ public static function getContactDashlets($contactID = NULL) { 'url' => $item['dashboard_id.url'], 'cache_minutes' => $item['dashboard_id.cache_minutes'], 'fullscreen_url' => CRM_Utils_Array::value('dashboard_id.fullscreen_url', $item), - ); + ]; } } // If empty, then initialize default dashlets for this user. if (!$results['count']) { // They may just have disabled all their dashlets. Check if any records exist for this contact. - if (!civicrm_api3('DashboardContact', 'getcount', array('contact_id' => $contactID))) { + if (!civicrm_api3('DashboardContact', 'getcount', ['contact_id' => $contactID, 'dashboard_id.domain_id' => CRM_Core_Config::domainID()])) { $dashlets = self::initializeDashlets(); } } @@ -154,16 +156,16 @@ public static function getContactDashlets($contactID = NULL) { * @return array */ public static function getContactDashletsForJS() { - $data = array(array(), array()); + $data = [[], []]; foreach (self::getContactDashlets() as $item) { - $data[$item['column_no']][] = array( + $data[$item['column_no']][] = [ 'id' => (int) $item['dashboard_id'], 'name' => $item['name'], 'title' => $item['label'], 'url' => self::parseUrl($item['url']), 'cacheMinutes' => $item['cache_minutes'], 'fullscreenUrl' => self::parseUrl($item['fullscreen_url']), - ); + ]; } return $data; } @@ -175,27 +177,27 @@ public static function getContactDashletsForJS() { * the default dashlets. * * @return array - * Array of dashboard_id's + * Array of dashboard_id's * @throws \CiviCRM_API3_Exception */ public static function initializeDashlets() { - $dashlets = array(); - $getDashlets = civicrm_api3("Dashboard", "get", array( - 'domain_id' => CRM_Core_Config::domainID(), - 'option.limit' => 0, - )); + $dashlets = []; + $getDashlets = civicrm_api3("Dashboard", "get", [ + 'domain_id' => CRM_Core_Config::domainID(), + 'option.limit' => 0, + ]); $contactID = CRM_Core_Session::getLoggedInContactID(); - $allDashlets = CRM_Utils_Array::index(array('name'), $getDashlets['values']); - $defaultDashlets = array(); - $defaults = array('blog' => 1, 'getting-started' => '0'); + $allDashlets = CRM_Utils_Array::index(['name'], $getDashlets['values']); + $defaultDashlets = []; + $defaults = ['blog' => 1, 'getting-started' => '0']; foreach ($defaults as $name => $column) { if (!empty($allDashlets[$name]) && !empty($allDashlets[$name]['id'])) { - $defaultDashlets[$name] = array( + $defaultDashlets[$name] = [ 'dashboard_id' => $allDashlets[$name]['id'], 'is_active' => 1, 'column_no' => $column, 'contact_id' => $contactID, - ); + ]; } } CRM_Utils_Hook::dashboard_defaults($allDashlets, $defaultDashlets); @@ -209,7 +211,7 @@ public static function initializeDashlets() { else { $assignDashlets = civicrm_api3("dashboard_contact", "create", $defaultDashlet); $values = $assignDashlets['values'][$assignDashlets['id']]; - $dashlets[$assignDashlets['id']] = array( + $dashlets[$assignDashlets['id']] = [ 'dashboard_id' => $values['dashboard_id'], 'weight' => $values['weight'], 'column_no' => $values['column_no'], @@ -218,7 +220,7 @@ public static function initializeDashlets() { 'cache_minutes' => $dashlet['cache_minutes'], 'url' => $dashlet['url'], 'fullscreen_url' => CRM_Utils_Array::value('fullscreen_url', $dashlet), - ); + ]; } } } @@ -271,10 +273,8 @@ public static function checkPermission($permission, $operator) { } // hack to handle case permissions - if (!$componentName && in_array($key, array( - 'access my cases and activities', - 'access all cases and activities', - )) + if (!$componentName + && in_array($key, ['access my cases and activities', 'access all cases and activities']) ) { $componentName = 'CiviCase'; } @@ -335,7 +335,7 @@ public static function saveDashletChanges($columns, $contactID = NULL) { throw new RuntimeException("Failed to determine contact ID"); } - $dashletIDs = array(); + $dashletIDs = []; if (is_array($columns)) { foreach ($columns as $colNo => $dashlets) { if (!is_int($colNo)) { @@ -357,13 +357,39 @@ public static function saveDashletChanges($columns, $contactID = NULL) { } } - // Disable inactive widgets - $dashletClause = $dashletIDs ? "dashboard_id NOT IN (" . implode(',', $dashletIDs) . ")" : '(1)'; + // Find dashlets in this domain. + $domainDashlets = civicrm_api3('Dashboard', 'get', [ + 'return' => array('id'), + 'domain_id' => CRM_Core_Config::domainID(), + 'options' => ['limit' => 0], + ]); + + // Get the array of IDs. + $domainDashletIDs = []; + if ($domainDashlets['is_error'] == 0) { + $domainDashletIDs = CRM_Utils_Array::collect('id', $domainDashlets['values']); + } + + // Restrict query to Dashlets in this domain. + $domainDashletClause = !empty($domainDashletIDs) ? "dashboard_id IN (" . implode(',', $domainDashletIDs) . ")" : '(1)'; + + // Target only those Dashlets which are inactive. + $dashletClause = $dashletIDs ? "dashboard_id NOT IN (" . implode(',', $dashletIDs) . ")" : '(1)'; + + // Build params. + $params = [ + 1 => [$contactID, 'Integer'], + ]; + + // Build query. $updateQuery = "UPDATE civicrm_dashboard_contact SET is_active = 0 - WHERE $dashletClause AND contact_id = {$contactID}"; + WHERE $domainDashletClause + AND $dashletClause + AND contact_id = %1"; - CRM_Core_DAO::executeQuery($updateQuery); + // Disable inactive widgets. + CRM_Core_DAO::executeQuery($updateQuery, $params); } /** @@ -389,7 +415,16 @@ public static function addDashlet(&$params) { $dashlet = new CRM_Core_DAO_Dashboard(); if (!$dashboardID) { - // check url is same as exiting entries, if yes just update existing + + // Assign domain before search to allow identical dashlets in different domains. + if (empty($params['domain_id'])) { + $dashlet->domain_id = CRM_Core_Config::domainID(); + } + else { + $dashlet->domain_id = CRM_Utils_Array::value('domain_id', $params); + } + + // Try and find an existing dashlet - it will be updated if found. if (!empty($params['name'])) { $dashlet->name = CRM_Utils_Array::value('name', $params); $dashlet->find(TRUE); @@ -398,9 +433,7 @@ public static function addDashlet(&$params) { $dashlet->url = CRM_Utils_Array::value('url', $params); $dashlet->find(TRUE); } - if (empty($params['domain_id'])) { - $dashlet->domain_id = CRM_Core_Config::domainID(); - } + } else { $dashlet->id = $dashboardID; @@ -428,7 +461,7 @@ public static function addContactDashlet($dashlet) { // if dashlet is created by admin then you need to add it all contacts. // else just add to contact who is creating this dashlet - $contactIDs = array(); + $contactIDs = []; if ($admin) { $query = "SELECT distinct( contact_id ) FROM civicrm_dashboard_contact"; @@ -475,7 +508,7 @@ public static function addContactDashlet($dashlet) { */ public static function addContactDashletToDashboard(&$params) { $valuesString = NULL; - $columns = array(); + $columns = []; foreach ($params as $dashboardIDs) { $contactID = CRM_Utils_Array::value('contact_id', $dashboardIDs); $dashboardID = CRM_Utils_Array::value('dashboard_id', $dashboardIDs); diff --git a/CRM/Core/BAO/Discount.php b/CRM/Core/BAO/Discount.php index 5fc881edf939..dfef62e32882 100644 --- a/CRM/Core/BAO/Discount.php +++ b/CRM/Core/BAO/Discount.php @@ -1,9 +1,9 @@ entity_id = $entityId; $dao->entity_table = $entityTable; diff --git a/CRM/Core/BAO/Domain.php b/CRM/Core/BAO/Domain.php index 5c519baf1f15..78460afdc42c 100644 --- a/CRM/Core/BAO/Domain.php +++ b/CRM/Core/BAO/Domain.php @@ -1,9 +1,9 @@ _location == NULL) { $domain = self::getDomain(NULL); - $params = array( + $params = [ 'contact_id' => $domain->contact_id, - ); + ]; $this->_location = CRM_Core_BAO_Location::getValues($params, TRUE); if (empty($this->_location)) { @@ -125,10 +127,12 @@ public function &getLocationValues() { * domain */ public static function edit(&$params, &$id) { + CRM_Utils_Hook::pre('edit', 'Domain', CRM_Utils_Array::value('id', $params), $params); $domain = new CRM_Core_DAO_Domain(); $domain->id = $id; $domain->copyValues($params); $domain->save(); + CRM_Utils_Hook::post('edit', 'Domain', $domain->id, $domain); return $domain; } @@ -141,9 +145,12 @@ public static function edit(&$params, &$id) { * domain */ public static function create($params) { + $hook = empty($params['id']) ? 'create' : 'edit'; + CRM_Utils_Hook::pre($hook, 'Domain', CRM_Utils_Array::value('id', $params), $params); $domain = new CRM_Core_DAO_Domain(); - $domain->copyValues($params); + $domain->copyValues($params, TRUE); $domain->save(); + CRM_Utils_Hook::post($hook, 'Domain', $domain->id, $domain); return $domain; } @@ -164,30 +171,35 @@ public static function multipleDomains() { /** * @param bool $skipFatal - * + * @param bool $returnString * @return array * name & email for domain * @throws Exception */ - public static function getNameAndEmail($skipFatal = FALSE) { + public static function getNameAndEmail($skipFatal = FALSE, $returnString = FALSE) { $fromEmailAddress = CRM_Core_OptionGroup::values('from_email_address', NULL, NULL, NULL, ' AND is_default = 1'); if (!empty($fromEmailAddress)) { + if ($returnString) { + // Return a string like: "Demonstrators Anonymous" + return $fromEmailAddress; + } foreach ($fromEmailAddress as $key => $value) { $email = CRM_Utils_Mail::pluckEmailFromHeader($value); $fromArray = explode('"', $value); $fromName = CRM_Utils_Array::value(1, $fromArray); break; } - return array($fromName, $email); + return [$fromName, $email]; } - elseif ($skipFatal) { - return array('', ''); + + if ($skipFatal) { + return [NULL, NULL]; } - $url = CRM_Utils_System::url('civicrm/admin/domain', - 'action=update&reset=1' + $url = CRM_Utils_System::url('civicrm/admin/options/from_email_address', + 'reset=1' ); - $status = ts("There is no valid default from email address configured for the domain. You can configure here Configure From Email Address.", array(1 => $url)); + $status = ts("There is no valid default from email address configured for the domain. You can configure here Configure From Email Address.", [1 => $url]); CRM_Core_Error::fatal($status); } @@ -201,7 +213,7 @@ public static function addContactToDomainGroup($contactID) { $groupID = self::getGroupId(); if ($groupID) { - $contactIDs = array($contactID); + $contactIDs = [$contactID]; CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIDs, $groupID); return $groupID; @@ -252,7 +264,7 @@ public static function isDomainGroup($groupId) { */ public static function getChildGroupIds() { $domainGroupID = self::getGroupId(); - $childGrps = array(); + $childGrps = []; if ($domainGroupID) { $childGrps = CRM_Contact_BAO_GroupNesting::getChildGroupIds($domainGroupID); @@ -268,7 +280,7 @@ public static function getChildGroupIds() { */ public static function getContactList() { $siteGroups = CRM_Core_BAO_Domain::getChildGroupIds(); - $siteContacts = array(); + $siteContacts = []; if (!empty($siteGroups)) { $query = " @@ -287,26 +299,39 @@ public static function getContactList() { /** * CRM-20308 & CRM-19657 - * Return domain information / user information for the useage in receipts - * Try default from adress then fall back to using logged in user details + * Return domain information / user information for the usage in receipts + * Try default from address then fall back to using logged in user details */ public static function getDefaultReceiptFrom() { - $domain = civicrm_api3('domain', 'getsingle', array('id' => CRM_Core_Config::domainID())); + $domain = civicrm_api3('domain', 'getsingle', ['id' => CRM_Core_Config::domainID()]); if (!empty($domain['from_email'])) { - return array($domain['from_name'], $domain['from_email']); + return [$domain['from_name'], $domain['from_email']]; } if (!empty($domain['domain_email'])) { - return array($domain['name'], $domain['domain_email']); + return [$domain['name'], $domain['domain_email']]; } - $userID = CRM_Core_Session::singleton()->getLoggedInContactID(); $userName = ''; $userEmail = ''; + + if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) { + return [$userName, $userEmail]; + } + + $userID = CRM_Core_Session::singleton()->getLoggedInContactID(); if (!empty($userID)) { list($userName, $userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($userID); } // If still empty fall back to the logged in user details. // return empty values no matter what. - return array($userName, $userEmail); + return [$userName, $userEmail]; + } + + /** + * Get address to be used for system from addresses when a reply is not expected. + */ + public static function getNoReplyEmailAddress() { + $emailDomain = CRM_Core_BAO_MailSettings::defaultDomain(); + return "do-not-reply@$emailDomain"; } } diff --git a/CRM/Core/BAO/Email.php b/CRM/Core/BAO/Email.php index 3bfee2724e89..20c6875d6a68 100644 --- a/CRM/Core/BAO/Email.php +++ b/CRM/Core/BAO/Email.php @@ -1,9 +1,9 @@ array( + $params = [ + 1 => [ $id, 'Integer', - ), - ); + ], + ]; - $emails = $values = array(); + $emails = $values = []; $dao = CRM_Core_DAO::executeQuery($query, $params); $count = 1; while ($dao->fetch()) { - $values = array( + $values = [ 'locationType' => $dao->locationType, 'is_primary' => $dao->is_primary, 'on_hold' => $dao->on_hold, 'id' => $dao->email_id, 'email' => $dao->email, 'locationTypeId' => $dao->locationTypeId, - ); + ]; if ($updateBlankLocInfo) { $emails[$count++] = $values; @@ -207,24 +207,24 @@ public static function allEntityEmails(&$entityElements) { AND ltype.id = e.location_type_id ORDER BY e.is_primary DESC, email_id ASC "; - $params = array( - 1 => array( + $params = [ + 1 => [ $entityId, 'Integer', - ), - ); + ], + ]; - $emails = array(); + $emails = []; $dao = CRM_Core_DAO::executeQuery($sql, $params); while ($dao->fetch()) { - $emails[$dao->email_id] = array( + $emails[$dao->email_id] = [ 'locationType' => $dao->locationType, 'is_primary' => $dao->is_primary, 'on_hold' => $dao->on_hold, 'id' => $dao->email_id, 'email' => $dao->email, 'locationTypeId' => $dao->locationTypeId, - ); + ]; } return $emails; @@ -237,10 +237,20 @@ public static function allEntityEmails(&$entityElements) { * Email object. */ public static function holdEmail(&$email) { + if ($email->id && $email->on_hold === NULL) { + // email is being updated but no change to on_hold. + return; + } + if ($email->on_hold === 'null' || $email->on_hold === NULL) { + // legacy handling, deprecated. + $email->on_hold = 0; + } + $email->on_hold = (int) $email->on_hold; + //check for update mode if ($email->id) { - $params = array(1 => array($email->id, 'Integer')); - if ($email->on_hold && $email->on_hold != 'null') { + $params = [1 => [$email->id, 'Integer']]; + if ($email->on_hold) { $sql = " SELECT id FROM civicrm_email @@ -252,7 +262,8 @@ public static function holdEmail(&$email) { $email->reset_date = 'null'; } } - elseif ($email->on_hold == 'null') { + elseif ($email->on_hold === 0) { + // we do this lookup to see if reset_date should be changed. $sql = " SELECT id FROM civicrm_email @@ -269,12 +280,26 @@ public static function holdEmail(&$email) { } } else { - if (($email->on_hold != 'null') && $email->on_hold) { + if ($email->on_hold) { $email->hold_date = date('YmdHis'); } } } + /** + * Generate an array of Domain email addresses. + * @return array $domainEmails; + */ + public static function domainEmails() { + $domainEmails = []; + $domainFrom = (array) CRM_Core_OptionGroup::values('from_email_address'); + foreach (array_keys($domainFrom) as $k) { + $domainEmail = $domainFrom[$k]; + $domainEmails[$domainEmail] = htmlspecialchars($domainEmail); + } + return $domainEmails; + } + /** * Build From Email as the combination of all the email ids of the logged in user and * the domain email id @@ -283,22 +308,21 @@ public static function holdEmail(&$email) { * an array of email ids */ public static function getFromEmail() { - $contactID = CRM_Core_Session::singleton()->getLoggedInContactID(); - $fromEmailValues = array(); - // add all configured FROM email addresses - $domainFrom = CRM_Core_OptionGroup::values('from_email_address'); - foreach (array_keys($domainFrom) as $k) { - $domainEmail = $domainFrom[$k]; - $fromEmailValues[$domainEmail] = htmlspecialchars($domainEmail); + $fromEmailValues = self::domainEmails(); + + if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) { + return $fromEmailValues; } + $contactFromEmails = []; // add logged in user's active email ids + $contactID = CRM_Core_Session::singleton()->getLoggedInContactID(); if ($contactID) { $contactEmails = self::allEmails($contactID); - $fromDisplayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'display_name'); + $fromDisplayName = CRM_Core_Session::singleton()->getLoggedInContactDisplayName(); - foreach ($contactEmails as $emailVal) { + foreach ($contactEmails as $emailId => $emailVal) { $email = trim($emailVal['email']); if (!$email || $emailVal['on_hold']) { continue; @@ -309,10 +333,10 @@ public static function getFromEmail() { if (!empty($emailVal['is_primary'])) { $fromEmailHtml .= ' ' . ts('(preferred)'); } - $fromEmailValues[$fromEmail] = $fromEmailHtml; + $contactFromEmails[$emailId] = $fromEmailHtml; } } - return $fromEmailValues; + return CRM_Utils_Array::crmArrayMerge($contactFromEmails, $fromEmailValues); } /** diff --git a/CRM/Core/BAO/EntityTag.php b/CRM/Core/BAO/EntityTag.php index accc5d6d0826..e7ec387c7971 100644 --- a/CRM/Core/BAO/EntityTag.php +++ b/CRM/Core/BAO/EntityTag.php @@ -1,9 +1,9 @@ entity_id = $entityID; @@ -89,7 +89,7 @@ public static function add(&$params) { //invoke post hook on entityTag // we are using this format to keep things consistent between the single and bulk operations // so a bit different from other post hooks - $object = array(0 => array(0 => $params['entity_id']), 1 => $params['entity_table']); + $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']]; CRM_Utils_Hook::post('create', 'EntityTag', $params['tag_id'], $object); } return $entityTag; @@ -125,7 +125,7 @@ public static function del(&$params) { //invoke post hook on entityTag if (!empty($params['tag_id'])) { - $object = array(0 => array(0 => $params['entity_id']), 1 => $params['entity_table']); + $object = [0 => [0 => $params['entity_id']], 1 => $params['entity_table']]; CRM_Utils_Hook::post('delete', 'EntityTag', $params['tag_id'], $object); } } @@ -148,10 +148,10 @@ public static function del(&$params) { public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable, $applyPermissions) { $numEntitiesAdded = 0; $numEntitiesNotAdded = 0; - $entityIdsAdded = array(); + $entityIdsAdded = []; //invoke pre hook for entityTag - $preObject = array($entityIds, $entityTable); + $preObject = [$entityIds, $entityTable]; CRM_Utils_Hook::pre('create', 'EntityTag', $tagId, $preObject); foreach ($entityIds as $entityId) { @@ -177,12 +177,12 @@ public static function addEntitiesToTag(&$entityIds, $tagId, $entityTable, $appl } //invoke post hook on entityTag - $object = array($entityIdsAdded, $entityTable); + $object = [$entityIdsAdded, $entityTable]; CRM_Utils_Hook::post('create', 'EntityTag', $tagId, $object); CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); - return array(count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded); + return [count($entityIds), $numEntitiesAdded, $numEntitiesNotAdded]; } /** @@ -226,10 +226,10 @@ public static function checkPermissionOnEntityTag($entityID, $entityTable) { public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable, $applyPermissions) { $numEntitiesRemoved = 0; $numEntitiesNotRemoved = 0; - $entityIdsRemoved = array(); + $entityIdsRemoved = []; //invoke pre hook for entityTag - $preObject = array($entityIds, $entityTable); + $preObject = [$entityIds, $entityTable]; CRM_Utils_Hook::pre('delete', 'EntityTag', $tagId, $preObject); foreach ($entityIds as $entityId) { @@ -255,12 +255,12 @@ public static function removeEntitiesFromTag(&$entityIds, $tagId, $entityTable, } //invoke post hook on entityTag - $object = array($entityIdsRemoved, $entityTable); + $object = [$entityIdsRemoved, $entityTable]; CRM_Utils_Hook::post('delete', 'EntityTag', $tagId, $object); CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); - return array(count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved); + return [count($entityIds), $numEntitiesRemoved, $numEntitiesNotRemoved]; } /** @@ -280,12 +280,12 @@ public static function create(&$params, $entityTable, $entityID) { // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input if (!is_array($params)) { - $params = array(); + $params = []; } // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input if (!is_array($entityTag)) { - $entityTag = array(); + $entityTag = []; } // check which values has to be inserted/deleted for contact @@ -315,7 +315,7 @@ public static function create(&$params, $entityTable, $entityID) { * array of entity ids */ public function getEntitiesByTag($tag) { - $entityIds = array(); + $entityIds = []; $entityTagDAO = new CRM_Core_DAO_EntityTag(); $entityTagDAO->tag_id = $tag->id; $entityTagDAO->find(); @@ -334,7 +334,7 @@ public function getEntitiesByTag($tag) { * @return array */ public static function getContactTags($contactID, $count = FALSE) { - $contactTags = array(); + $contactTags = []; if (!$count) { $select = "SELECT ct.id, ct.name "; } @@ -373,7 +373,7 @@ public static function getContactTags($contactID, $count = FALSE) { * @return array */ public static function getChildEntityTags($parentId, $entityId, $entityTable = 'civicrm_contact') { - $entityTags = array(); + $entityTags = []; $query = "SELECT ct.id as tag_id, name FROM civicrm_tag ct INNER JOIN civicrm_entity_tag et ON ( et.entity_id = {$entityId} AND et.entity_table = '{$entityTable}' AND et.tag_id = ct.id) @@ -382,10 +382,10 @@ public static function getChildEntityTags($parentId, $entityId, $entityTable = ' $dao = CRM_Core_DAO::executeQuery($query); while ($dao->fetch()) { - $entityTags[$dao->tag_id] = array( + $entityTags[$dao->tag_id] = [ 'id' => $dao->tag_id, 'name' => $dao->name, - ); + ]; } return $entityTags; @@ -403,26 +403,26 @@ public static function getChildEntityTags($parentId, $entityId, $entityTable = ' * @return array */ public function mergeTags($tagAId, $tagBId) { - $queryParams = array( - 1 => array($tagAId, 'Integer'), - 2 => array($tagBId, 'Integer'), - ); + $queryParams = [ + 1 => [$tagAId, 'Integer'], + 2 => [$tagBId, 'Integer'], + ]; // re-compute used_for field $query = "SELECT id, name, used_for FROM civicrm_tag WHERE id IN (%1, %2)"; $dao = CRM_Core_DAO::executeQuery($query, $queryParams); - $tags = array(); + $tags = []; while ($dao->fetch()) { $label = ($dao->id == $tagAId) ? 'tagA' : 'tagB'; $tags[$label] = $dao->name; - $tags["{$label}_used_for"] = $dao->used_for ? explode(",", $dao->used_for) : array(); + $tags["{$label}_used_for"] = $dao->used_for ? explode(",", $dao->used_for) : []; } $usedFor = array_merge($tags["tagA_used_for"], $tags["tagB_used_for"]); $usedFor = implode(',', array_unique($usedFor)); $tags["used_for"] = explode(",", $usedFor); // get all merge queries together - $sqls = array( + $sqls = [ // 1. update entity tag entries "UPDATE IGNORE civicrm_entity_tag SET tag_id = %1 WHERE tag_id = %2", // 2. move children @@ -435,8 +435,8 @@ public function mergeTags($tagAId, $tagBId) { "DELETE et2.* from civicrm_entity_tag et1 INNER JOIN civicrm_entity_tag et2 ON et1.entity_table = et2.entity_table AND et1.entity_id = et2.entity_id AND et1.tag_id = et2.tag_id WHERE et1.id < et2.id", // 6. remove orphaned entity_tags "DELETE FROM civicrm_entity_tag WHERE tag_id = %2", - ); - $tables = array('civicrm_entity_tag', 'civicrm_tag'); + ]; + $tables = ['civicrm_entity_tag', 'civicrm_tag']; // Allow hook_civicrm_merge() to add SQL statements for the merge operation AND / OR // perform any other actions like logging @@ -467,8 +467,8 @@ public function mergeTags($tagAId, $tagBId) { * * @return array|bool */ - public static function buildOptions($fieldName, $context = NULL, $props = array()) { - $params = array(); + public static function buildOptions($fieldName, $context = NULL, $props = []) { + $params = []; if ($fieldName == 'tag' || $fieldName == 'tag_id') { if (!empty($props['entity_table'])) { @@ -479,7 +479,7 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( // Output tag list as nested hierarchy // TODO: This will only work when api.entity is "entity_tag". What about others? if ($context == 'search' || $context == 'create') { - $dummyArray = array(); + $dummyArray = []; return CRM_Core_BAO_Tag::getTags(CRM_Utils_Array::value('entity_table', $props, 'civicrm_contact'), $dummyArray, CRM_Utils_Array::value('parent_id', $params), '- '); } } @@ -487,8 +487,8 @@ public static function buildOptions($fieldName, $context = NULL, $props = array( $options = CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context); // Special formatting for validate/match context - if ($fieldName == 'entity_table' && in_array($context, array('validate', 'match'))) { - $options = array(); + if ($fieldName == 'entity_table' && in_array($context, ['validate', 'match'])) { + $options = []; foreach (self::buildOptions($fieldName) as $tableName => $label) { $bao = CRM_Core_DAO_AllCoreTables::getClassForTable($tableName); $apiName = CRM_Core_DAO_AllCoreTables::getBriefName($bao); diff --git a/CRM/Core/BAO/Extension.php b/CRM/Core/BAO/Extension.php index ebfcc5c16e82..8bbe63fffacf 100644 --- a/CRM/Core/BAO/Extension.php +++ b/CRM/Core/BAO/Extension.php @@ -1,9 +1,9 @@ array($schemaVersion, 'String'), - 2 => array($fullName, 'String'), - ); + $params = [ + 1 => [$schemaVersion, 'String'], + 2 => [$fullName, 'String'], + ]; return CRM_Core_DAO::executeQuery($sql, $params); } @@ -98,9 +98,9 @@ public static function setSchemaVersion($fullName, $schemaVersion) { */ public static function getSchemaVersion($fullName) { $sql = 'SELECT schema_version FROM civicrm_extension WHERE full_name = %1'; - $params = array( - 1 => array($fullName, 'String'), - ); + $params = [ + 1 => [$fullName, 'String'], + ]; return CRM_Core_DAO::singleValueQuery($sql, $params); } diff --git a/CRM/Core/BAO/File.php b/CRM/Core/BAO/File.php index 21e1e0787408..7bf3ae3918b3 100644 --- a/CRM/Core/BAO/File.php +++ b/CRM/Core/BAO/File.php @@ -1,9 +1,9 @@ copyValues($params); + + if (empty($params['id']) && empty($params['created_id'])) { + $fileDAO->created_id = CRM_Core_Session::getLoggedInContactID(); + } + + $fileDAO->save(); + + CRM_Utils_Hook::post($op, 'File', $fileDAO->id, $fileDAO); + + return $fileDAO; + } /** * @param int $fileID * @param int $entityID - * @param null $entityTable * * @return array */ - public static function path($fileID, $entityID, $entityTable = NULL) { + public static function path($fileID, $entityID) { $entityFileDAO = new CRM_Core_DAO_EntityFile(); - if ($entityTable) { - $entityFileDAO->entity_table = $entityTable; - } $entityFileDAO->entity_id = $entityID; $entityFileDAO->file_id = $fileID; @@ -63,15 +94,14 @@ public static function path($fileID, $entityID, $entityTable = NULL) { $path = $config->customFileUploadDir . $fileDAO->uri; if (file_exists($path) && is_readable($path)) { - return array($path, $fileDAO->mime_type); + return [$path, $fileDAO->mime_type]; } } } - return array(NULL, NULL); + return [NULL, NULL]; } - /** * @param $data * @param int $fileTypeID @@ -97,7 +127,7 @@ public static function filePostProcess( $mimeType = NULL ) { if (!$mimeType) { - CRM_Core_Error::fatal(ts('Mime Type is now a required parameter')); + CRM_Core_Error::statusBounce(ts('Mime Type is now a required parameter for file upload')); } $config = CRM_Core_Config::singleton(); @@ -116,7 +146,7 @@ public static function filePostProcess( CRM_Utils_File::createDir($directoryName); if (!rename($data, $directoryName . DIRECTORY_SEPARATOR . $filename)) { - CRM_Core_Error::fatal(ts('Could not move custom file to custom upload directory')); + CRM_Core_Error::statusBounce(ts('Could not move custom file to custom upload directory')); } // to get id's @@ -187,7 +217,7 @@ public static function deleteFileReferences($fileID, $entityID, $fieldID) { $fileDAO = new CRM_Core_DAO_File(); $fileDAO->id = $fileID; if (!$fileDAO->find(TRUE)) { - CRM_Core_Error::fatal(); + throw new CRM_Core_Exception(ts('File not found')); } // lets call a pre hook before the delete, so attachments hooks can get the info before things @@ -203,7 +233,7 @@ public static function deleteFileReferences($fileID, $entityID, $fieldID) { $entityFileDAO->entity_table = $tableName; if (!$entityFileDAO->find(TRUE)) { - CRM_Core_Error::fatal(); + throw new CRM_Core_Exception(sprintf('No record found for given file ID - %d and entity ID - %d', $fileID, $entityID)); } $entityFileDAO->delete(); @@ -211,7 +241,7 @@ public static function deleteFileReferences($fileID, $entityID, $fieldID) { // also set the value to null of the table and column $query = "UPDATE $tableName SET $columnName = null WHERE $columnName = %1"; - $params = array(1 => array($fileID, 'Integer')); + $params = [1 => [$fileID, 'Integer']]; CRM_Core_DAO::executeQuery($query, $params); } @@ -246,8 +276,8 @@ public static function deleteEntityFile($entityTable, $entityID, $fileTypeID = N list($sql, $params) = self::sql($entityTable, $entityID, $fileTypeID, $fileID); $dao = CRM_Core_DAO::executeQuery($sql, $params); - $cfIDs = array(); - $cefIDs = array(); + $cfIDs = []; + $cefIDs = []; while ($dao->fetch()) { $cfIDs[$dao->cfID] = $dao->uri; $cefIDs[] = $dao->cefID; @@ -262,13 +292,13 @@ public static function deleteEntityFile($entityTable, $entityID, $fileTypeID = N if (!empty($cfIDs)) { // Delete file only if there no any entity using this file. - $deleteFiles = array(); + $deleteFiles = []; foreach ($cfIDs as $fId => $fUri) { //delete tags from entity tag table - $tagParams = array( + $tagParams = [ 'entity_table' => 'civicrm_file', 'entity_id' => $fId, - ); + ]; CRM_Core_BAO_EntityTag::del($tagParams); @@ -307,8 +337,9 @@ public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = F list($sql, $params) = self::sql($entityTable, $entityID, NULL); $dao = CRM_Core_DAO::executeQuery($sql, $params); - $results = array(); + $results = []; while ($dao->fetch()) { + $fileHash = self::generateFileHash($dao->entity_id, $dao->cfID); $result['fileID'] = $dao->cfID; $result['entityID'] = $dao->cefID; $result['mime_type'] = $dao->mime_type; @@ -316,7 +347,7 @@ public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = F $result['description'] = $dao->description; $result['cleanName'] = CRM_Utils_File::cleanFileName($dao->uri); $result['fullPath'] = $config->customFileUploadDir . DIRECTORY_SEPARATOR . $dao->uri; - $result['url'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$dao->cfID}&eid={$dao->entity_id}"); + $result['url'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$dao->cfID}&eid={$dao->entity_id}&fcs={$fileHash}"); $result['href'] = "{$result['cleanName']}"; $result['tag'] = CRM_Core_BAO_EntityTag::getTag($dao->cfID, 'civicrm_file'); $result['icon'] = CRM_Utils_File::getIconFromMimeType($dao->mime_type); @@ -327,11 +358,11 @@ public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = F } //fix tag names - $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); + $tags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); foreach ($results as &$values) { if (!empty($values['tag'])) { - $tagNames = array(); + $tagNames = []; foreach ($values['tag'] as $tid) { $tagNames[] = $tags[$tid]; } @@ -342,7 +373,6 @@ public static function getEntityFile($entityTable, $entityID, $addDeleteArgs = F } } - $dao->free(); return $results; } @@ -386,22 +416,22 @@ public static function sql($entityTable, $entityID, $fileTypeID = NULL, $fileID AND CEF.entity_id = %2"; } - $params = array( - 1 => array($entityTable, 'String'), - 2 => array($entityID, 'Integer'), - ); + $params = [ + 1 => [$entityTable, 'String'], + 2 => [$entityID, 'Integer'], + ]; if ($fileTypeID !== NULL) { $sql .= " AND CF.file_type_id = %3"; - $params[3] = array($fileTypeID, 'Integer'); + $params[3] = [$fileTypeID, 'Integer']; } if ($fileID !== NULL) { $sql .= " AND CF.id = %4"; - $params[4] = array($fileID, 'Integer'); + $params[4] = [$fileID, 'Integer']; } - return array($sql, $params); + return [$sql, $params]; } /** @@ -453,30 +483,30 @@ public static function buildAttachment(&$form, $entityTable, $entityID = NULL, $ // add attachments for ($i = 1; $i <= $numAttachments; $i++) { - $form->addElement('file', "attachFile_$i", ts('Attach File'), 'size=30 maxlength=60'); + $form->addElement('file', "attachFile_$i", ts('Attach File'), 'size=30 maxlength=221'); $form->addUploadElement("attachFile_$i"); $form->setMaxFileSize($maxFileSize * 1024 * 1024); $form->addRule("attachFile_$i", ts('File size should be less than %1 MByte(s)', - array(1 => $maxFileSize) + [1 => $maxFileSize] ), 'maxfilesize', $maxFileSize * 1024 * 1024 ); - $form->addElement('text', "attachDesc_$i", NULL, array( + $form->addElement('text', "attachDesc_$i", NULL, [ 'size' => 40, 'maxlength' => 255, 'placeholder' => ts('Description'), - )); + ]); if (!empty($tags)) { $form->add('select', "tag_$i", ts('Tags'), $tags, FALSE, - array( + [ 'id' => "tags_$i", 'multiple' => 'multiple', 'class' => 'huge crm-select2', 'placeholder' => ts('- none -'), - ) + ] ); } CRM_Core_Form_Tag::buildQuickForm($form, $parentNames, 'civicrm_file', NULL, FALSE, TRUE, "file_taglist_$i"); @@ -504,7 +534,7 @@ public static function attachmentInfo($entityTable, $entityID, $separator = '
    $attach) { $currentAttachmentURL[] = $attach['href']; } @@ -541,7 +571,7 @@ public static function formatAttachment( $attachFreeTags = "file_taglist_$i"; if (isset($formValues[$attachName]) && !empty($formValues[$attachName])) { // add static tags if selects - $tagParams = array(); + $tagParams = []; if (!empty($formValues[$attachTags])) { foreach ($formValues[$attachTags] as $tag) { $tagParams[$tag] = 1; @@ -550,11 +580,11 @@ public static function formatAttachment( // we dont care if the file is empty or not // CRM-7448 - $extraParams = array( + $extraParams = [ 'description' => $formValues[$attachDesc], 'tag' => $tagParams, - 'attachment_taglist' => CRM_Utils_Array::value($attachFreeTags, $formValues, array()), - ); + 'attachment_taglist' => CRM_Utils_Array::value($attachFreeTags, $formValues, []), + ]; CRM_Utils_File::formatFile($formValues, $attachName, $extraParams); @@ -573,24 +603,37 @@ public static function formatAttachment( * @param int $entityID */ public static function processAttachment(&$params, $entityTable, $entityID) { - $numAttachments = Civi::settings()->get('max_attachments'); + $numAttachments = Civi::settings()->get('max_attachments_backend') ?? self::DEFAULT_MAX_ATTACHMENTS_BACKEND; for ($i = 1; $i <= $numAttachments; $i++) { - if ( - isset($params["attachFile_$i"]) && - is_array($params["attachFile_$i"]) - ) { - self::filePostProcess( - $params["attachFile_$i"]['location'], - NULL, - $entityTable, - $entityID, - NULL, - TRUE, - $params["attachFile_$i"], - "attachFile_$i", - $params["attachFile_$i"]['type'] - ); + if (isset($params["attachFile_$i"])) { + /** + * Moved the second condition into its own if block to avoid changing + * how it works if there happens to be an entry that is not an array, + * since we now might exit loop early via newly added break below. + */ + if (is_array($params["attachFile_$i"])) { + self::filePostProcess( + $params["attachFile_$i"]['location'], + NULL, + $entityTable, + $entityID, + NULL, + TRUE, + $params["attachFile_$i"], + "attachFile_$i", + $params["attachFile_$i"]['type'] + ); + } + } + else { + /** + * No point looping 100 times if there aren't any more. + * This assumes the array is continuous and doesn't skip array keys, + * but (a) where would it be doing that, and (b) it would have caused + * problems before anyway if there were skipped keys. + */ + break; } } } @@ -601,7 +644,7 @@ public static function processAttachment(&$params, $entityTable, $entityID) { public static function uploadNames() { $numAttachments = Civi::settings()->get('max_attachments'); - $names = array(); + $names = []; for ($i = 1; $i <= $numAttachments; $i++) { $names[] = "attachFile_{$i}"; } @@ -655,7 +698,7 @@ public static function deleteURLArgs($entityTable, $entityID, $fileID) { * */ public static function deleteAttachment() { - $params = array(); + $params = []; $params['entityTable'] = CRM_Utils_Request::retrieve('entityTable', 'String', CRM_Core_DAO::$_nullObject, TRUE); $params['entityID'] = CRM_Utils_Request::retrieve('entityID', 'Positive', CRM_Core_DAO::$_nullObject, TRUE); $params['fileID'] = CRM_Utils_Request::retrieve('fileID', 'Positive', CRM_Core_DAO::$_nullObject, TRUE); @@ -670,7 +713,6 @@ public static function deleteAttachment() { self::deleteEntityFile($params['entityTable'], $params['entityID'], NULL, $params['fileID']); } - /** * Display paper icon for a file attachment -- CRM-13624 * @@ -731,7 +773,7 @@ public static function paperIconAttachment($entityTable, $entityID) { * @return CRM_Core_FileSearchInterface|NULL */ public static function getSearchService() { - $fileSearches = array(); + $fileSearches = []; CRM_Utils_Hook::fileSearches($fileSearches); // use the first available search @@ -742,4 +784,58 @@ public static function getSearchService() { return NULL; } + /** + * Generates an access-token for downloading a specific file. + * + * @param int $entityId entity id the file is attached to + * @param int $fileId file ID + * @param int $genTs + * @param int $life + * @return string + */ + public static function generateFileHash($entityId = NULL, $fileId = NULL, $genTs = NULL, $life = NULL) { + // Use multiple (but stable) inputs for hash information. + $siteKey = CRM_Utils_Constant::value('CIVICRM_SITE_KEY'); + if (!$siteKey) { + throw new \CRM_Core_Exception("Cannot generate file access token. Please set CIVICRM_SITE_KEY."); + } + + if (!$genTs) { + $genTs = time(); + } + if (!$life) { + $days = Civi::settings()->get('checksum_timeout'); + $life = 24 * $days; + } + // Trim 8 chars off the string, make it slightly easier to find + // but reveals less information from the hash. + $cs = hash_hmac('sha256', "entity={$entityId}&file={$fileId}&life={$life}", $siteKey); + return "{$cs}_{$genTs}_{$life}"; + } + + /** + * Validate a file access token. + * + * @param string $hash + * @param int $entityId Entity Id the file is attached to + * @param int $fileId File Id + * @return bool + */ + public static function validateFileHash($hash, $entityId, $fileId) { + $input = CRM_Utils_System::explode('_', $hash, 3); + $inputTs = CRM_Utils_Array::value(1, $input); + $inputLF = CRM_Utils_Array::value(2, $input); + $testHash = CRM_Core_BAO_File::generateFileHash($entityId, $fileId, $inputTs, $inputLF); + if (hash_equals($testHash, $hash)) { + $now = time(); + if ($inputTs + ($inputLF * 60 * 60) >= $now) { + return TRUE; + } + else { + return FALSE; + } + } + return FALSE; + } + } diff --git a/CRM/Core/BAO/FinancialTrxn.php b/CRM/Core/BAO/FinancialTrxn.php index 31e04c38d3d0..9fe5b61e4689 100644 --- a/CRM/Core/BAO/FinancialTrxn.php +++ b/CRM/Core/BAO/FinancialTrxn.php @@ -1,9 +1,9 @@ copyValues($params); - if (!CRM_Utils_Rule::currencyCode($trxn->currency)) { + if (empty($params['id']) && !CRM_Utils_Rule::currencyCode($trxn->currency)) { $trxn->currency = CRM_Core_Config::singleton()->defaultCurrency; } $trxn->save(); - // save to entity_financial_trxn table - $entityFinancialTrxnParams - = array( - 'entity_table' => "civicrm_contribution", - 'financial_trxn_id' => $trxn->id, - 'amount' => $params['total_amount'], - ); - - if (!empty($trxnEntityTable)) { - $entityFinancialTrxnParams['entity_table'] = $trxnEntityTable['entity_table']; - $entityFinancialTrxnParams['entity_id'] = $trxnEntityTable['entity_id']; - } - else { - $entityFinancialTrxnParams['entity_id'] = $params['contribution_id']; + if (!empty($params['id'])) { + // For an update entity financial transaction record will already exist. Return early. + return $trxn; } + // Save to entity_financial_trxn table. + $entityFinancialTrxnParams = [ + 'entity_table' => CRM_Utils_Array::value('entity_table', $params, 'civicrm_contribution'), + 'entity_id' => CRM_Utils_Array::value('entity_id', $params, CRM_Utils_Array::value('contribution_id', $params)), + 'financial_trxn_id' => $trxn->id, + 'amount' => $params['total_amount'], + ]; + $entityTrxn = new CRM_Financial_DAO_EntityFinancialTrxn(); $entityTrxn->copyValues($entityFinancialTrxnParams); $entityTrxn->save(); @@ -100,11 +93,11 @@ public static function getBalanceTrxnAmt($contributionId, $contributionFinancial $toFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contributionFinancialTypeId, 'Accounts Receivable Account is'); $q = "SELECT ft.id, ft.total_amount FROM civicrm_financial_trxn ft INNER JOIN civicrm_entity_financial_trxn eft ON (eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution') WHERE eft.entity_id = %1 AND ft.to_financial_account_id = %2"; - $p[1] = array($contributionId, 'Integer'); - $p[2] = array($toFinancialAccount, 'Integer'); + $p[1] = [$contributionId, 'Integer']; + $p[2] = [$toFinancialAccount, 'Integer']; $balanceAmtDAO = CRM_Core_DAO::executeQuery($q, $p); - $ret = array(); + $ret = []; if ($balanceAmtDAO->N) { $ret['total_amount'] = 0; } @@ -124,9 +117,9 @@ public static function getBalanceTrxnAmt($contributionId, $contributionFinancial * @param array $defaults * (reference ) an assoc array to hold the flattened values. * - * @return CRM_Contribute_BAO_ContributionType + * @return \CRM_Financial_DAO_FinancialTrxn */ - public static function retrieve(&$params, &$defaults) { + public static function retrieve(&$params, &$defaults = []) { $financialItem = new CRM_Financial_DAO_FinancialTrxn(); $financialItem->copyValues($params); if ($financialItem->find(TRUE)) { @@ -147,15 +140,16 @@ public static function retrieve(&$params, &$defaults) { * @param bool $newTrxn * @param string $whereClause * Additional where parameters + * @param int $fromAccountID * * @return array * array of category id's the contact belongs to. * */ public static function getFinancialTrxnId($entity_id, $orderBy = 'ASC', $newTrxn = FALSE, $whereClause = '', $fromAccountID = NULL) { - $ids = array('entityFinancialTrxnId' => NULL, 'financialTrxnId' => NULL); + $ids = ['entityFinancialTrxnId' => NULL, 'financialTrxnId' => NULL]; - $params = array(1 => array($entity_id, 'Integer')); + $params = [1 => [$entity_id, 'Integer']]; $condition = ""; if (!$newTrxn) { $condition = " AND ((ceft1.entity_table IS NOT NULL) OR (cft.payment_instrument_id IS NOT NULL AND ceft1.entity_table IS NULL)) "; @@ -163,7 +157,7 @@ public static function getFinancialTrxnId($entity_id, $orderBy = 'ASC', $newTrxn if ($fromAccountID) { $condition .= " AND (cft.from_financial_account_id <> %2 OR cft.from_financial_account_id IS NULL)"; - $params[2] = array($fromAccountID, 'Integer'); + $params[2] = [$fromAccountID, 'Integer']; } if ($orderBy) { $orderBy = CRM_Utils_Type::escape($orderBy, 'String'); @@ -205,7 +199,7 @@ public static function getRefundTransactionTrxnID($contributionID) { * Get the transaction id for the (latest) refund associated with a contribution. * * @param int $contributionID - * @return string + * @return array */ public static function getRefundTransactionIDs($contributionID) { $refundStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); @@ -230,7 +224,7 @@ public static function getFinancialTrxnTotal($entity_id) { WHERE ft.entity_table = 'civicrm_contribution' AND ft.entity_id = %1 "; - $sqlParams = array(1 => array($entity_id, 'Integer')); + $sqlParams = [1 => [$entity_id, 'Integer']]; return CRM_Core_DAO::singleValueQuery($query, $sqlParams); } @@ -263,10 +257,10 @@ public static function getPayments($financial_trxn_id) { AND ef2.entity_table = 'civicrm_financial_trxn' AND ef1.entity_table = 'civicrm_financial_trxn'"; - $sqlParams = array(1 => array($financial_trxn_id, 'Integer')); + $sqlParams = [1 => [$financial_trxn_id, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); $i = 0; - $result = array(); + $result = []; while ($dao->fetch()) { $result[$i]['financial_trxn_id'] = $dao->financial_trxn_id; $result[$i]['amount'] = $dao->amount; @@ -275,7 +269,7 @@ public static function getPayments($financial_trxn_id) { if (empty($result)) { $query = "SELECT sum( amount ) amount FROM civicrm_entity_financial_trxn WHERE financial_trxn_id =%1 AND entity_table = 'civicrm_financial_item'"; - $sqlParams = array(1 => array($financial_trxn_id, 'Integer')); + $sqlParams = [1 => [$financial_trxn_id, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); if ($dao->fetch()) { @@ -305,7 +299,7 @@ public static function getFinancialTrxnLineTotal($entity_id, $entity_table = 'ci LEFT JOIN civicrm_line_item AS lt ON lt.id = fi.entity_id AND lt.entity_table = %2 WHERE lt.entity_id = %1 "; - $sqlParams = array(1 => array($entity_id, 'Integer'), 2 => array($entity_table, 'String')); + $sqlParams = [1 => [$entity_id, 'Integer'], 2 => [$entity_table, 'String']]; $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); while ($dao->fetch()) { $result[$dao->financial_trxn_id][$dao->id] = $dao->amount; @@ -334,7 +328,7 @@ public static function deleteFinancialTrxn($entity_id) { LEFT JOIN civicrm_financial_item cfi ON ceft1.entity_table = 'civicrm_financial_item' and cfi.id = ceft1.entity_id WHERE ceft.entity_id = %1"; - CRM_Core_DAO::executeQuery($query, array(1 => array($entity_id, 'Integer'))); + CRM_Core_DAO::executeQuery($query, [1 => [$entity_id, 'Integer']]); return TRUE; } @@ -358,33 +352,32 @@ public static function createPremiumTrxn($params) { $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); $toFinancialAccountType = !empty($params['isDeleted']) ? 'Premiums Inventory Account is' : 'Cost of Sales Account is'; $fromFinancialAccountType = !empty($params['isDeleted']) ? 'Cost of Sales Account is' : 'Premiums Inventory Account is'; - $accountRelationship = array_flip($accountRelationship); - $financialtrxn = array( + $financialtrxn = [ 'to_financial_account_id' => CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['financial_type_id'], $toFinancialAccountType), 'from_financial_account_id' => CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($params['financial_type_id'], $fromFinancialAccountType), 'trxn_date' => date('YmdHis'), 'total_amount' => CRM_Utils_Array::value('cost', $params) ? $params['cost'] : 0, 'currency' => CRM_Utils_Array::value('currency', $params), 'status_id' => array_search('Completed', $contributionStatuses), - ); - $trxnEntityTable['entity_table'] = 'civicrm_contribution'; - $trxnEntityTable['entity_id'] = $params['contributionId']; - CRM_Core_BAO_FinancialTrxn::create($financialtrxn, $trxnEntityTable); + 'entity_table' => 'civicrm_contribution', + 'entity_id' => $params['contributionId'], + ]; + CRM_Core_BAO_FinancialTrxn::create($financialtrxn); } if (!empty($params['oldPremium'])) { - $premiumParams = array( + $premiumParams = [ 'id' => $params['oldPremium']['product_id'], - ); - $productDetails = array(); - CRM_Contribute_BAO_ManagePremiums::retrieve($premiumParams, $productDetails); - $params = array( + ]; + $productDetails = []; + CRM_Contribute_BAO_Product::retrieve($premiumParams, $productDetails); + $params = [ 'cost' => CRM_Utils_Array::value('cost', $productDetails), 'currency' => CRM_Utils_Array::value('currency', $productDetails), 'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $productDetails), 'contributionId' => $params['oldPremium']['contribution_id'], 'isDeleted' => TRUE, - ); + ]; CRM_Core_BAO_FinancialTrxn::createPremiumTrxn($params); } } @@ -395,7 +388,7 @@ public static function createPremiumTrxn($params) { * @param array $params * To create trxn entries. * - * @return bool + * @return bool|void */ public static function recordFees($params) { $domainId = CRM_Core_Config::domainID(); @@ -414,7 +407,7 @@ public static function recordFees($params) { else { $financialTypeId = $params['financial_type_id']; } - $financialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeId, 'Expense Account is'); + $financialAccount = CRM_Financial_BAO_FinancialAccount::getFinancialAccountForFinancialTypeByRelationship($financialTypeId, 'Expense Account is'); $params['trxnParams']['from_financial_account_id'] = $params['to_financial_account_id']; $params['trxnParams']['to_financial_account_id'] = $financialAccount; @@ -429,142 +422,91 @@ public static function recordFees($params) { $params['entity_id'] = $financialTrxnID['financialTrxnId']; } $fItemParams - = array( + = [ 'financial_account_id' => $financialAccount, 'contact_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Domain', $domainId, 'contact_id'), 'created_date' => date('YmdHis'), 'transaction_date' => date('YmdHis'), 'amount' => $amount, 'description' => 'Fee', - 'status_id' => CRM_Core_Pseudoconstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'), + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_FinancialItem', 'status_id', 'Paid'), 'entity_table' => 'civicrm_financial_trxn', 'entity_id' => $params['entity_id'], 'currency' => $params['trxnParams']['currency'], - ); + ]; $trxnIDS['id'] = $trxn->id; CRM_Financial_BAO_FinancialItem::create($fItemParams, NULL, $trxnIDS); } /** - * get partial payment amount and type of it. + * get partial payment amount. + * + * @deprecated + * + * This function basically calls CRM_Contribute_BAO_Contribution::getContributionBalance + * - just do that. If need be we could have a fn to get the contribution id but + * chances are the calling functions already know it anyway. * * @param int $entityId * @param string $entityName - * @param bool $returnType * @param int $lineItemTotal * - * @return array|int|NULL|string - * [payment type => amount] - * payment type: 'amount_owed' or 'refund_due' + * @return array */ - public static function getPartialPaymentWithType($entityId, $entityName = 'participant', $returnType = TRUE, $lineItemTotal = NULL) { + public static function getPartialPaymentWithType($entityId, $entityName = 'participant', $lineItemTotal = NULL) { + CRM_Core_Error::deprecatedFunctionWarning('CRM_Contribute_BAO_Contribution::getContributionBalance'); $value = NULL; if (empty($entityName)) { return $value; } + // @todo - deprecate passing in entity & type - just figure out contribution id FIRST if ($entityName == 'participant') { $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $entityId, 'contribution_id', 'participant_id'); } + elseif ($entityName == 'membership') { + $contributionId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $entityId, 'contribution_id', 'membership_id'); + } else { $contributionId = $entityId; } $financialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'financial_type_id'); if ($contributionId && $financialTypeId) { - $statusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); - $refundStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); - - $filteredFinancialAccounts = array(); - $filteredFinancialAccountRel = array( - 'Accounts Receivable Account is', - 'Expense Account is', - ); - if (CRM_Contribute_BAO_Contribution::checkContributeSettings('deferred_revenue_enabled')) { - $filteredFinancialAccountRel = array_merge($filteredFinancialAccountRel, array( - 'Deferred Revenue Account is', - 'Income Account is', - )); - } - foreach ($filteredFinancialAccountRel as $financialAccountRel) { - $financialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($financialTypeId, $financialAccountRel); - if ($financialAccount) { - $filteredFinancialAccounts[] = $financialAccount; - } + $paymentVal = CRM_Contribute_BAO_Contribution::getContributionBalance($contributionId, $lineItemTotal); + $value = []; + if ($paymentVal < 0) { + $value['refund_due'] = $paymentVal; } - - if (empty($lineItemTotal)) { - $lineItemTotal = CRM_Price_BAO_LineItem::getLineTotal($contributionId); - } - $sqlFtTotalAmt = " -SELECT SUM(ft.total_amount) -FROM civicrm_financial_trxn ft - INNER JOIN civicrm_entity_financial_trxn eft ON (ft.id = eft.financial_trxn_id AND eft.entity_table = 'civicrm_contribution' AND eft.entity_id = {$contributionId}) -WHERE ft.to_financial_account_id NOT IN ( " . implode(', ', $filteredFinancialAccounts) . " ) - AND ft.status_id IN ({$statusId}, {$refundStatusId}) -"; - - $ftTotalAmt = CRM_Core_DAO::singleValueQuery($sqlFtTotalAmt); - $value = 0; - if (!$ftTotalAmt) { - $ftTotalAmt = 0; - } - $value = $paymentVal = $lineItemTotal - $ftTotalAmt; - if ($returnType) { - $value = array(); - if ($paymentVal < 0) { - $value['refund_due'] = $paymentVal; - } - elseif ($paymentVal > 0) { - $value['amount_owed'] = $paymentVal; - } - elseif ($lineItemTotal == $ftTotalAmt) { - $value['full_paid'] = $ftTotalAmt; - } + elseif ($paymentVal > 0) { + $value['amount_owed'] = $paymentVal; } } return $value; } /** - * @param int $contributionId + * @param int $contributionID + * @param bool $includeRefund * - * @return array + * @return string */ - public static function getTotalPayments($contributionId) { - $statusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); + public static function getTotalPayments($contributionID, $includeRefund = FALSE) { + $statusIDs = [CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed')]; + + if ($includeRefund) { + $statusIDs[] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); + } $sql = "SELECT SUM(ft.total_amount) FROM civicrm_financial_trxn ft INNER JOIN civicrm_entity_financial_trxn eft ON (eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution') - WHERE eft.entity_id = %1 AND ft.is_payment = 1 AND ft.status_id = %2"; + WHERE eft.entity_id = %1 AND ft.is_payment = 1 AND ft.status_id IN (%2) "; - $params = array( - 1 => array($contributionId, 'Integer'), - 2 => array($statusId, 'Integer'), - ); - - return CRM_Core_DAO::singleValueQuery($sql, $params); - } - - /** - * Function records partial payment, complete's contribution if payment is fully paid - * and returns latest payment ie financial trxn - * - * @param array $contribution - * @param array $params - * - * @return CRM_Core_BAO_FinancialTrxn - */ - public static function getPartialPaymentTrxn($contribution, $params) { - $trxn = CRM_Contribute_BAO_Contribution::recordPartialPayment($contribution, $params); - $paid = CRM_Core_BAO_FinancialTrxn::getTotalPayments($params['contribution_id']); - $total = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $params['contribution_id'], 'total_amount'); - $cmp = bccomp($total, $paid, 5); - if ($cmp == 0 || $cmp == -1) {// If paid amount is greater or equal to total amount - civicrm_api3('Contribution', 'completetransaction', array('id' => $contribution['id'])); - } - return $trxn; + return CRM_Core_DAO::singleValueQuery($sql, [ + 1 => [$contributionID, 'Integer'], + 2 => [implode(',', $statusIDs), 'CommaSeparatedIntegers'], + ]); } /** @@ -575,10 +517,10 @@ public static function getPartialPaymentTrxn($contribution, $params) { * @return array */ public static function getMembershipRevenueAmount($lineItem) { - $revenueAmount = array(); - $membershipDetail = civicrm_api3('Membership', 'getsingle', array( + $revenueAmount = []; + $membershipDetail = civicrm_api3('Membership', 'getsingle', [ 'id' => $lineItem['entity_id'], - )); + ]); if (empty($membershipDetail['end_date'])) { return $revenueAmount; } @@ -631,7 +573,7 @@ public static function createDeferredTrxn($lineItems, $contributionDetails, $upd ) { return; } - $trxnParams = array( + $trxnParams = [ 'contribution_id' => $contributionDetails->id, 'fee_amount' => '0.00', 'currency' => $contributionDetails->currency, @@ -639,9 +581,9 @@ public static function createDeferredTrxn($lineItems, $contributionDetails, $upd 'status_id' => $contributionDetails->contribution_status_id, 'payment_instrument_id' => $contributionDetails->payment_instrument_id, 'check_number' => $contributionDetails->check_number, - ); + ]; - $deferredRevenues = array(); + $deferredRevenues = []; foreach ($lineItems as $priceSetID => $lineItem) { if (!$priceSetID) { continue; @@ -656,12 +598,12 @@ public static function createDeferredTrxn($lineItems, $contributionDetails, $upd $deferredRevenues[$key]['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_LineItem', $item['id'], 'financial_type_id'); } if (in_array($item['entity_table'], - array('civicrm_participant', 'civicrm_contribution')) + ['civicrm_participant', 'civicrm_contribution']) ) { - $deferredRevenues[$key]['revenue'][] = array( + $deferredRevenues[$key]['revenue'][] = [ 'amount' => $lineTotal, 'revenue_date' => $revenueRecognitionDate, - ); + ]; } else { // for membership @@ -675,32 +617,32 @@ public static function createDeferredTrxn($lineItems, $contributionDetails, $upd CRM_Utils_Hook::alterDeferredRevenueItems($deferredRevenues, $contributionDetails, $update, $context); foreach ($deferredRevenues as $key => $deferredRevenue) { - $results = civicrm_api3('EntityFinancialAccount', 'get', array( + $results = civicrm_api3('EntityFinancialAccount', 'get', [ 'entity_table' => 'civicrm_financial_type', 'entity_id' => $deferredRevenue['financial_type_id'], - 'account_relationship' => array('IN' => array('Income Account is', 'Deferred Revenue Account is')), - )); + 'account_relationship' => ['IN' => ['Income Account is', 'Deferred Revenue Account is']], + ]); if ($results['count'] != 2) { continue; } foreach ($results['values'] as $result) { if ($result['account_relationship'] == $accountRel) { - $trxnParams['to_financial_account_id'] = $result['financial_account_id']; + $trxnParams['from_financial_account_id'] = $result['financial_account_id']; } else { - $trxnParams['from_financial_account_id'] = $result['financial_account_id']; + $trxnParams['to_financial_account_id'] = $result['financial_account_id']; } } foreach ($deferredRevenue['revenue'] as $revenue) { $trxnParams['total_amount'] = $trxnParams['net_amount'] = $revenue['amount']; $trxnParams['trxn_date'] = CRM_Utils_Date::isoToMysql($revenue['revenue_date']); $financialTxn = CRM_Core_BAO_FinancialTrxn::create($trxnParams); - $entityParams = array( + $entityParams = [ 'entity_id' => $deferredRevenue['financial_item_id'], 'entity_table' => 'civicrm_financial_item', 'amount' => $revenue['amount'], 'financial_trxn_id' => $financialTxn->id, - ); + ]; civicrm_api3('EntityFinancialTrxn', 'create', $entityParams); } } @@ -716,13 +658,13 @@ public static function createDeferredTrxn($lineItems, $contributionDetails, $upd * */ public static function updateCreditCardDetails($contributionID, $panTruncation, $cardType) { - $financialTrxn = civicrm_api3('EntityFinancialTrxn', 'get', array( - 'return' => array('financial_trxn_id.payment_processor_id', 'financial_trxn_id'), + $financialTrxn = civicrm_api3('EntityFinancialTrxn', 'get', [ + 'return' => ['financial_trxn_id.payment_processor_id', 'financial_trxn_id'], 'entity_table' => 'civicrm_contribution', 'entity_id' => $contributionID, 'financial_trxn_id.is_payment' => TRUE, - 'options' => array('sort' => 'financial_trxn_id DESC', 'limit' => 1), - )); + 'options' => ['sort' => 'financial_trxn_id DESC', 'limit' => 1], + ]); // In case of Contribution status is Pending From Incomplete Transaction or Failed there is no Financial Entries created for Contribution. // Above api will return 0 count, in such case we won't update card type and pan truncation field. @@ -738,7 +680,7 @@ public static function updateCreditCardDetails($contributionID, $panTruncation, } $financialTrxnId = $financialTrxn['financial_trxn_id']; - $trxnparams = array('id' => $financialTrxnId); + $trxnparams = ['id' => $financialTrxnId]; if (isset($cardType)) { $trxnparams['card_type_id'] = $cardType; } @@ -748,4 +690,71 @@ public static function updateCreditCardDetails($contributionID, $panTruncation, civicrm_api3('FinancialTrxn', 'create', $trxnparams); } + /** + * The function is responsible for handling financial entries if payment instrument is changed + * + * @param array $inputParams + * + */ + public static function updateFinancialAccountsOnPaymentInstrumentChange($inputParams) { + $prevContribution = $inputParams['prevContribution']; + $currentContribution = $inputParams['contribution']; + // ensure that there are all the information in updated contribution object identified by $currentContribution + $currentContribution->find(TRUE); + + $deferredFinancialAccount = CRM_Utils_Array::value('deferred_financial_account_id', $inputParams); + if (empty($deferredFinancialAccount)) { + $deferredFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($prevContribution->financial_type_id, 'Deferred Revenue Account is'); + } + + $lastFinancialTrxnId = self::getFinancialTrxnId($prevContribution->id, 'DESC', FALSE, NULL, $deferredFinancialAccount); + + // there is no point to proceed as we can't find the last payment made + // @todo we should throw an exception here rather than return false. + if (empty($lastFinancialTrxnId['financialTrxnId'])) { + return FALSE; + } + + // If payment instrument is changed reverse the last payment + // in terms of reversing financial item and trxn + $lastFinancialTrxn = civicrm_api3('FinancialTrxn', 'getsingle', ['id' => $lastFinancialTrxnId['financialTrxnId']]); + unset($lastFinancialTrxn['id']); + $lastFinancialTrxn['trxn_date'] = $inputParams['trxnParams']['trxn_date']; + $lastFinancialTrxn['total_amount'] = -$inputParams['trxnParams']['total_amount']; + $lastFinancialTrxn['net_amount'] = -$inputParams['trxnParams']['net_amount']; + $lastFinancialTrxn['fee_amount'] = -$inputParams['trxnParams']['fee_amount']; + $lastFinancialTrxn['contribution_id'] = $prevContribution->id; + foreach ([$lastFinancialTrxn, $inputParams['trxnParams']] as $financialTrxnParams) { + $trxn = CRM_Core_BAO_FinancialTrxn::create($financialTrxnParams); + $trxnParams = [ + 'total_amount' => $trxn->total_amount, + 'contribution_id' => $currentContribution->id, + ]; + CRM_Contribute_BAO_Contribution::assignProportionalLineItems($trxnParams, $trxn->id, $prevContribution->total_amount); + } + + self::createDeferredTrxn(CRM_Utils_Array::value('line_item', $inputParams), $currentContribution, TRUE, 'changePaymentInstrument'); + + return TRUE; + } + + /** + * Generate and assign an arbitrary value to a field of a test object. + * + * Always set is_payment to 1 as this is used for Payment api as well as FinancialTrxn. + * + * @param string $fieldName + * @param array $fieldDef + * @param int $counter + * The globally-unique ID of the test object. + */ + protected function assignTestValue($fieldName, &$fieldDef, $counter) { + if ($fieldName === 'is_payment') { + $this->is_payment = 1; + } + else { + parent::assignTestValue($fieldName, $fieldDef, $counter); + } + } + } diff --git a/CRM/Core/BAO/IM.php b/CRM/Core/BAO/IM.php index c23b28058e15..a64d22341ede 100644 --- a/CRM/Core/BAO/IM.php +++ b/CRM/Core/BAO/IM.php @@ -1,9 +1,9 @@ array($id, 'Integer')); + $params = [1 => [$id, 'Integer']]; - $ims = $values = array(); + $ims = $values = []; $dao = CRM_Core_DAO::executeQuery($query, $params); $count = 1; while ($dao->fetch()) { - $values = array( + $values = [ 'locationType' => $dao->locationType, 'is_primary' => $dao->is_primary, 'id' => $dao->im_id, 'name' => $dao->im, 'locationTypeId' => $dao->locationTypeId, 'providerId' => $dao->providerId, - ); + ]; if ($updateBlankLocInfo) { $ims[$count++] = $values; @@ -147,18 +147,18 @@ public static function allEntityIMs(&$entityElements) { AND ltype.id = cim.location_type_id ORDER BY cim.is_primary DESC, im_id ASC "; - $params = array(1 => array($entityId, 'Integer')); + $params = [1 => [$entityId, 'Integer']]; - $ims = array(); + $ims = []; $dao = CRM_Core_DAO::executeQuery($sql, $params); while ($dao->fetch()) { - $ims[$dao->im_id] = array( + $ims[$dao->im_id] = [ 'locationType' => $dao->locationType, 'is_primary' => $dao->is_primary, 'id' => $dao->im_id, 'name' => $dao->im, 'locationTypeId' => $dao->locationTypeId, - ); + ]; } return $ims; } diff --git a/CRM/Core/BAO/Job.php b/CRM/Core/BAO/Job.php index 2034fccba35a..867a0433754a 100644 --- a/CRM/Core/BAO/Job.php +++ b/CRM/Core/BAO/Job.php @@ -1,9 +1,9 @@ [ + 'name' => ' - ' . ts('Copy'), + ], + 'replace' => $params, + ]; + $copy = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_Job', ['id' => $id], NULL, $fieldsFix); + $copy->save(); + CRM_Utils_Hook::copy('Job', $copy); + + return $copy; + } + } diff --git a/CRM/Core/BAO/LabelFormat.php b/CRM/Core/BAO/LabelFormat.php index ba4ca660e4f3..3ff7966611b0 100644 --- a/CRM/Core/BAO/LabelFormat.php +++ b/CRM/Core/BAO/LabelFormat.php @@ -1,7 +1,7 @@ array( + private static $optionValueFields = [ + 'paper-size' => [ // Paper size: names defined in option_value table (option_group = 'paper_size') 'name' => 'paper-size', 'type' => CRM_Utils_Type::T_STRING, 'default' => 'letter', - ), - 'orientation' => array( + ], + 'orientation' => [ // Paper orientation: 'portrait' or 'landscape' 'name' => 'orientation', 'type' => CRM_Utils_Type::T_STRING, 'default' => 'portrait', - ), - 'font-name' => array( + ], + 'font-name' => [ // Font name: 'courier', 'helvetica', 'times' 'name' => 'font-name', 'type' => CRM_Utils_Type::T_STRING, 'default' => 'helvetica', - ), - 'font-size' => array( + ], + 'font-size' => [ // Font size: always in points 'name' => 'font-size', 'type' => CRM_Utils_Type::T_INT, 'default' => 8, - ), - 'font-style' => array( + ], + 'font-style' => [ // Font style: 'B' bold, 'I' italic, 'BI' bold+italic 'name' => 'font-style', 'type' => CRM_Utils_Type::T_STRING, 'default' => '', - ), - 'NX' => array( + ], + 'NX' => [ // Number of labels horizontally 'name' => 'NX', 'type' => CRM_Utils_Type::T_INT, 'default' => 3, - ), - 'NY' => array( + ], + 'NY' => [ // Number of labels vertically 'name' => 'NY', 'type' => CRM_Utils_Type::T_INT, 'default' => 10, - ), - 'metric' => array( + ], + 'metric' => [ // Unit of measurement for all of the following fields 'name' => 'metric', 'type' => CRM_Utils_Type::T_STRING, 'default' => 'mm', - ), - 'lMargin' => array( + ], + 'lMargin' => [ // Left margin 'name' => 'lMargin', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 4.7625, - ), - 'tMargin' => array( + ], + 'tMargin' => [ // Right margin 'name' => 'tMargin', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 12.7, - ), - 'SpaceX' => array( + ], + 'SpaceX' => [ // Horizontal space between two labels 'name' => 'SpaceX', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 3.96875, - ), - 'SpaceY' => array( + ], + 'SpaceY' => [ // Vertical space between two labels 'name' => 'SpaceY', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 0, - ), - 'width' => array( + ], + 'width' => [ // Width of label 'name' => 'width', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 65.875, - ), - 'height' => array( + ], + 'height' => [ // Height of label 'name' => 'height', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 25.4, - ), - 'lPadding' => array( + ], + 'lPadding' => [ // Space between text and left edge of label 'name' => 'lPadding', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 5.08, - ), - 'tPadding' => array( + ], + 'tPadding' => [ // Space between text and top edge of label 'name' => 'tPadding', 'type' => CRM_Utils_Type::T_FLOAT, 'metric' => TRUE, 'default' => 5.08, - ), - ); + ], + ]; /** * Get page orientations recognized by the DOMPDF package used to create PDF letters. @@ -159,10 +161,10 @@ class CRM_Core_BAO_LabelFormat extends CRM_Core_DAO_OptionValue { * array of page orientations */ public static function getPageOrientations() { - return array( + return [ 'portrait' => ts('Portrait'), 'landscape' => ts('Landscape'), - ); + ]; } /** @@ -186,9 +188,9 @@ public static function getFontNames($name = 'label_format') { * array of font sizes */ public static function getFontSizes() { - $fontSizes = array(); + $fontSizes = []; for ($i = 6; $i <= 60; $i++) { - $fontSizes[$i] = ts('%1 pt', array(1 => $i)); + $fontSizes[$i] = ts('%1 pt', [1 => $i]); } return $fontSizes; @@ -201,12 +203,12 @@ public static function getFontSizes() { * array of measurement units */ public static function getUnits() { - return array( + return [ 'in' => ts('Inches'), 'cm' => ts('Centimeters'), 'mm' => ts('Millimeters'), 'pt' => ts('Points'), - ); + ]; } /** @@ -216,11 +218,11 @@ public static function getUnits() { * array of alignments */ public static function getTextAlignments() { - return array( + return [ 'R' => ts('Right'), 'L' => ts('Left'), 'C' => ts('Center'), - ); + ]; } /** @@ -230,11 +232,11 @@ public static function getTextAlignments() { * array of alignments */ public static function getFontStyles() { - return array( + return [ '' => ts('Normal'), 'B' => ts('Bold'), 'I' => ts('Italic'), - ); + ]; } /** @@ -258,7 +260,7 @@ private static function _getGid($name = 'label_format') { /** * Add ordering fields to Label Format list. * - * @param array (reference) $list List of Label Formats + * @param array $list List of Label Formats * @param string $returnURL * URL of page calling this function. * @@ -283,7 +285,7 @@ public static function addOrder(&$list, $returnURL) { * (reference) label format list */ public static function &getList($namesOnly = FALSE, $groupName = 'label_format') { - static $list = array(); + static $list = []; if (self::_getGid($groupName)) { // get saved label formats from Option Value table $dao = new CRM_Core_DAO_OptionValue(); @@ -313,13 +315,13 @@ public static function &getList($namesOnly = FALSE, $groupName = 'label_format') * Name/value pairs containing the default Label Format values. */ public static function &getDefaultValues($groupName = 'label_format') { - $params = array('is_active' => 1, 'is_default' => 1); - $defaults = array(); + $params = ['is_active' => 1, 'is_default' => 1]; + $defaults = []; if (!self::retrieve($params, $defaults, $groupName)) { foreach (self::$optionValueFields as $name => $field) { $defaults[$name] = $field['default']; } - $filter = array('option_group_id' => self::_getGid($groupName)); + $filter = ['option_group_id' => self::_getGid($groupName)]; $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue', $filter); } return $defaults; @@ -339,8 +341,8 @@ public static function &getDefaultValues($groupName = 'label_format') { * (reference) associative array of name/value pairs */ public static function &getLabelFormat($field, $val, $groupName = 'label_format') { - $params = array('is_active' => 1, $field => $val); - $labelFormat = array(); + $params = ['is_active' => 1, $field => $val]; + $labelFormat = []; if (self::retrieve($params, $labelFormat, $groupName)) { return $labelFormat; } @@ -382,7 +384,7 @@ public static function &getById($id, $groupName = 'label_format') { * * @param string $field * Name of a label format field. - * @param array (reference) $values associative array of name/value pairs containing + * @param array $values associative array of name/value pairs containing * label format field selections * * @param null $default @@ -458,7 +460,7 @@ public static function customGroupName() { /** * Save the Label Format in the DB. * - * @param array (reference) $values associative array of name/value pairs + * @param array $values associative array of name/value pairs * @param int $id * Id of the database record (null = new record). * @param string $groupName @@ -519,7 +521,7 @@ public function saveLabelFormat(&$values, $id = NULL, $groupName = 'label_format $this->save(); // fix duplicate weights - $filter = array('option_group_id' => self::_getGid()); + $filter = ['option_group_id' => self::_getGid()]; CRM_Utils_Weight::correctDuplicateWeights('CRM_Core_DAO_OptionValue', $filter); } @@ -537,7 +539,7 @@ public static function del($id, $groupName) { $dao->id = $id; if ($dao->find(TRUE)) { if ($dao->option_group_id == self::_getGid($groupName)) { - $filter = array('option_group_id' => self::_getGid($groupName)); + $filter = ['option_group_id' => self::_getGid($groupName)]; CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $id, $filter); $dao->delete(); return; diff --git a/CRM/Core/BAO/Location.php b/CRM/Core/BAO/Location.php index 56953d9b7d33..b8e7f073fea4 100644 --- a/CRM/Core/BAO/Location.php +++ b/CRM/Core/BAO/Location.php @@ -1,9 +1,9 @@ $params['entity_table'], 'entity_id' => $params['entity_id'], - ); + ]; $location['id'] = self::createLocBlock($location, $entityElements); } @@ -83,7 +84,7 @@ public static function create(&$params, $fixAddress = TRUE, $entity = NULL) { // when we come from a form which displays all the location elements (like the edit form or the inline block // elements, we can skip the below check. The below check adds quite a feq queries to an already overloaded // form - if (!CRM_Utils_Array::value('updateBlankLocInfo', $params, FALSE)) { + if (empty($params['updateBlankLocInfo'])) { // make sure contact should have only one primary block, CRM-5051 self::checkPrimaryBlocks(CRM_Utils_Array::value('contact_id', $params)); } @@ -102,18 +103,18 @@ public static function create(&$params, $fixAddress = TRUE, $entity = NULL) { */ public static function createLocBlock(&$location, &$entityElements) { $locId = self::findExisting($entityElements); - $locBlock = array(); + $locBlock = []; if ($locId) { $locBlock['id'] = $locId; } - foreach (array( - 'phone', - 'email', - 'im', - 'address', - ) as $loc) { + foreach ([ + 'phone', + 'email', + 'im', + 'address', + ] as $loc) { $locBlock["{$loc}_id"] = !empty($location["$loc"][0]) ? $location["$loc"][0]->id : NULL; $locBlock["{$loc}_2_id"] = !empty($location["$loc"][1]) ? $location["$loc"][1]->id : NULL; } @@ -150,7 +151,7 @@ public static function findExisting($entityElements) { FROM {$etable} e WHERE e.id = %1"; - $params = array(1 => array($eid, 'Integer')); + $params = [1 => [$eid, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); while ($dao->fetch()) { $locBlockId = $dao->locId; @@ -192,7 +193,7 @@ public static function deleteLocBlock($locBlockId) { $locBlock->find(TRUE); //resolve conflict of having same ids for multiple blocks - $store = array( + $store = [ 'IM_1' => $locBlock->im_id, 'IM_2' => $locBlock->im_2_id, 'Email_1' => $locBlock->email_id, @@ -201,7 +202,7 @@ public static function deleteLocBlock($locBlockId) { 'Phone_2' => $locBlock->phone_2_id, 'Address_1' => $locBlock->address_id, 'Address_2' => $locBlock->address_2_id, - ); + ]; $locBlock->delete(); foreach ($store as $daoName => $id) { if ($id) { @@ -210,7 +211,6 @@ public static function deleteLocBlock($locBlockId) { $dao->id = $id; $dao->find(TRUE); $dao->delete(); - $dao->free(); } } } @@ -249,12 +249,12 @@ public static function &getValues($entityBlock, $microformat = FALSE) { if (empty($entityBlock)) { return NULL; } - $blocks = array(); - $name_map = array( + $blocks = []; + $name_map = [ 'im' => 'IM', 'openid' => 'OpenID', - ); - $blocks = array(); + ]; + $blocks = []; //get all the blocks for this contact foreach (self::$blocks as $block) { if (array_key_exists($block, $name_map)) { @@ -293,9 +293,9 @@ public static function deleteLocationBlocks($contactId, $locationTypeId) { $locationTypeId = 'null'; } - static $blocks = array('Address', 'Phone', 'IM', 'OpenID', 'Email'); + static $blocks = ['Address', 'Phone', 'IM', 'OpenID', 'Email']; - $params = array('contact_id' => $contactId, 'location_type_id' => $locationTypeId); + $params = ['contact_id' => $contactId, 'location_type_id' => $locationTypeId]; foreach ($blocks as $name) { CRM_Core_BAO_Block::blockDelete($name, $params); } @@ -313,14 +313,15 @@ public static function deleteLocationBlocks($contactId, $locationTypeId) { * newly created/updated location block id. */ public static function copyLocBlock($locBlockId, $updateLocBlockId = NULL) { + CRM_Core_Error::deprecatedFunctionWarning('unused function which will be removed'); //get the location info. - $defaults = $updateValues = array(); - $locBlock = array('id' => $locBlockId); + $defaults = $updateValues = []; + $locBlock = ['id' => $locBlockId]; CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_LocBlock', $locBlock, $defaults); if ($updateLocBlockId) { //get the location info for update. - $copyLocationParams = array('id' => $updateLocBlockId); + $copyLocationParams = ['id' => $updateLocBlockId]; CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_LocBlock', $copyLocationParams, $updateValues); foreach ($updateValues as $key => $value) { if ($key != 'id') { @@ -336,16 +337,16 @@ public static function copyLocBlock($locBlockId, $updateLocBlockId = NULL) { $name = ucfirst($tbl[0]); $updateParams = NULL; if ($updateId = CRM_Utils_Array::value($key, $updateValues)) { - $updateParams = array('id' => $updateId); + $updateParams = ['id' => $updateId]; } - $copy = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_' . $name, array('id' => $value), $updateParams); + $copy = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_' . $name, ['id' => $value], $updateParams); $copyLocationParams[$key] = $copy->id; } } - $copyLocation = &CRM_Core_DAO::copyGeneric('CRM_Core_DAO_LocBlock', - array('id' => $locBlock['id']), + $copyLocation = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_LocBlock', + ['id' => $locBlock['id']], $copyLocationParams ); return $copyLocation->id; @@ -363,16 +364,16 @@ public static function checkPrimaryBlocks($contactId) { } // get the loc block ids. - $primaryLocBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($contactId, array('is_primary' => 1)); - $nonPrimaryBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($contactId, array('is_primary' => 0)); - - foreach (array( - 'Email', - 'IM', - 'Phone', - 'Address', - 'OpenID', - ) as $block) { + $primaryLocBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($contactId, ['is_primary' => 1]); + $nonPrimaryBlockIds = CRM_Contact_BAO_Contact::getLocBlockIds($contactId, ['is_primary' => 0]); + + foreach ([ + 'Email', + 'IM', + 'Phone', + 'Address', + 'OpenID', + ] as $block) { $name = strtolower($block); if (array_key_exists($name, $primaryLocBlockIds) && !CRM_Utils_System::isNull($primaryLocBlockIds[$name]) @@ -407,10 +408,10 @@ public static function checkPrimaryBlocks($contactId) { */ public static function getChainSelectValues($values, $valueType, $flatten = FALSE) { if (!$values) { - return array(); + return []; } $values = array_filter((array) $values); - $elements = array(); + $elements = []; $list = &$elements; $method = $valueType == 'country' ? 'stateProvinceForCountry' : 'countyForState'; foreach ($values as $val) { @@ -429,17 +430,17 @@ public static function getChainSelectValues($values, $valueType, $flatten = FALS else { // Option-groups for multiple categories if ($result && count($values) > 1) { - $elements[] = array( + $elements[] = [ 'value' => CRM_Core_PseudoConstant::$valueType($val, FALSE), - 'children' => array(), - ); + 'children' => [], + ]; $list = &$elements[count($elements) - 1]['children']; } foreach ($result as $id => $name) { - $list[] = array( + $list[] = [ 'value' => $name, 'key' => $id, - ); + ]; } } } diff --git a/CRM/Core/BAO/LocationType.php b/CRM/Core/BAO/LocationType.php index 9eead4bad16d..7f4cdab2df50 100644 --- a/CRM/Core/BAO/LocationType.php +++ b/CRM/Core/BAO/LocationType.php @@ -1,9 +1,9 @@ 1); - $defaults = array(); + $params = ['is_default' => 1]; + $defaults = []; self::$_defaultLocationType = self::retrieve($params, $defaults); } return self::$_defaultLocationType; @@ -107,7 +107,7 @@ public static function &getDefault() { */ public static function getBilling() { if (self::$_billingLocationType == NULL) { - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array(), 'validate'); + $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', [], 'validate'); self::$_billingLocationType = array_search('Billing', $locationTypes); } return self::$_billingLocationType; @@ -148,7 +148,7 @@ public static function create(&$params) { * */ public static function del($locationTypeId) { - $entity = array('address', 'phone', 'email', 'im'); + $entity = ['address', 'phone', 'email', 'im']; //check dependencies foreach ($entity as $key) { if ($key == 'im') { diff --git a/CRM/Core/BAO/Log.php b/CRM/Core/BAO/Log.php index 98765340101b..eb75c8370835 100644 --- a/CRM/Core/BAO/Log.php +++ b/CRM/Core/BAO/Log.php @@ -1,9 +1,9 @@ entity_id = $id; $log->orderBy('modified_date desc'); $log->limit(1); - $result = NULL; + $displayName = $result = $contactImage = NULL; if ($log->find(TRUE)) { - list($displayName, $contactImage) = CRM_Contact_BAO_Contact::getDisplayAndImage($log->modified_id); - $result = array( + if ($log->modified_id) { + list($displayName, $contactImage) = CRM_Contact_BAO_Contact::getDisplayAndImage($log->modified_id); + } + $result = [ 'id' => $log->modified_id, 'name' => $displayName, 'image' => $contactImage, 'date' => $log->modified_date, - ); + ]; } return $result; } @@ -91,7 +94,7 @@ public static function register( $userID = NULL ) { if (!self::$_processed) { - self::$_processed = array(); + self::$_processed = []; } if (!$userID) { @@ -125,7 +128,7 @@ public static function register( self::$_processed[$contactID][$userID] = 1; } else { - self::$_processed[$contactID] = array($userID => 1); + self::$_processed[$contactID] = [$userID => 1]; } $logData = "$tableName,$tableID"; @@ -180,8 +183,8 @@ public static function useLoggingReport() { $loggingSchema = new CRM_Logging_Schema(); if ($loggingSchema->isEnabled()) { - $params = array('report_id' => 'logging/contact/summary'); - $instance = array(); + $params = ['report_id' => 'logging/contact/summary']; + $instance = []; CRM_Report_BAO_ReportInstance::retrieve($params, $instance); if (!empty($instance) && diff --git a/CRM/Core/BAO/MailSettings.php b/CRM/Core/BAO/MailSettings.php index ef806d5ea9d6..05168c3e3ac0 100644 --- a/CRM/Core/BAO/MailSettings.php +++ b/CRM/Core/BAO/MailSettings.php @@ -1,9 +1,9 @@ array(CRM_Core_Config::domainID(), 'Integer')); + $queryParams = [1 => [CRM_Core_Config::domainID(), 'Integer']]; CRM_Core_DAO::executeQuery($query, $queryParams); } diff --git a/CRM/Core/BAO/Mapping.php b/CRM/Core/BAO/Mapping.php index 61827bcba99d..de2508f65bd8 100644 --- a/CRM/Core/BAO/Mapping.php +++ b/CRM/Core/BAO/Mapping.php @@ -1,9 +1,9 @@ mapping_type_id = $mappingTypeId; - $mappingDAO->find(); - - while ($mappingDAO->fetch()) { - $mapping[$mappingDAO->id] = $mappingDAO->name; + public static function getMappings($mappingType, $select2 = FALSE) { + $result = civicrm_api3('Mapping', 'get', [ + 'mapping_type_id' => $mappingType, + 'return' => ['name', 'description'], + 'options' => [ + 'sort' => 'name', + 'limit' => 0, + ], + ]); + $mapping = []; + + foreach ($result['values'] as $id => $value) { + if ($select2) { + $item = ['id' => $id, 'text' => $value['name']]; + if (!empty($value['description'])) { + $item['description'] = $value['description']; + } + $mapping[] = $item; + } + else { + $mapping[$id] = $value['name']; + } } - return $mapping; } + /** + * Get the mappings array, creating if it does not exist. + * + * @param string $mappingType + * Mapping type name. + * + * @return array + * Array of mapping names, keyed by id. + * + * @throws \CiviCRM_API3_Exception + */ + public static function getCreateMappingValues($mappingType) { + try { + return CRM_Core_BAO_Mapping::getMappings($mappingType); + } + catch (CiviCRM_API3_Exception $e) { + // Having a valid mapping_type_id is now enforced. However, rather than error let's + // add it. This is required for Multi value which could be done by upgrade script, but + // it feels like there could be other instances so this is safer. + $errorParams = $e->getExtraParams(); + if ($errorParams['error_field'] === 'mapping_type_id') { + $mappingValues = civicrm_api3('Mapping', 'getoptions', ['field' => 'mapping_type_id']); + civicrm_api3('OptionValue', 'create', [ + 'option_group_id' => 'mapping_type', + 'label' => $mappingType, + 'value' => max(array_keys($mappingValues['values'])) + 1, + 'is_reserved' => 1, + ]); + return CRM_Core_BAO_Mapping::getMappings($mappingType); + } + throw $e; + } + } + /** * Get the mapping fields. * * @param int $mappingId * Mapping id. * + * @param bool $addPrimary + * Add the key 'Primary' when the field is a location field AND there is + * no location type (meaning Primary)? + * * @return array * array of mapping fields */ - public static function getMappingFields($mappingId) { + public static function getMappingFields($mappingId, $addPrimary = FALSE) { //mapping is to be loaded from database $mapping = new CRM_Core_DAO_MappingField(); $mapping->mapping_id = $mappingId; $mapping->orderBy('column_number'); $mapping->find(); - $mappingName = $mappingLocation = $mappingContactType = $mappingPhoneType = array(); - $mappingImProvider = $mappingRelation = $mappingOperator = $mappingValue = $mappingWebsiteType = array(); + $mappingName = $mappingLocation = $mappingContactType = $mappingPhoneType = []; + $mappingImProvider = $mappingRelation = $mappingOperator = $mappingValue = $mappingWebsiteType = []; while ($mapping->fetch()) { $mappingName[$mapping->grouping][$mapping->column_number] = $mapping->name; $mappingContactType[$mapping->grouping][$mapping->column_number] = $mapping->contact_type; @@ -152,6 +205,14 @@ public static function getMappingFields($mappingId) { if (!empty($mapping->location_type_id)) { $mappingLocation[$mapping->grouping][$mapping->column_number] = $mapping->location_type_id; } + elseif ($addPrimary) { + if (CRM_Contact_BAO_Contact::isFieldHasLocationType($mapping->name)) { + $mappingLocation[$mapping->grouping][$mapping->column_number] = ts('Primary'); + } + else { + $mappingLocation[$mapping->grouping][$mapping->column_number] = NULL; + } + } if (!empty($mapping->phone_type_id)) { $mappingPhoneType[$mapping->grouping][$mapping->column_number] = $mapping->phone_type_id; @@ -179,7 +240,7 @@ public static function getMappingFields($mappingId) { } } - return array( + return [ $mappingName, $mappingContactType, $mappingLocation, @@ -189,7 +250,23 @@ public static function getMappingFields($mappingId) { $mappingOperator, $mappingValue, $mappingWebsiteType, - ); + ]; + } + + /** + * Get un-indexed array of the field values for the given mapping id. + * + * For example if passing a mapping ID & name the returned array would look like + * ['First field name', 'second field name'] + * + * @param int $mappingID + * @param string $fieldName + * + * @return array + * @throws \CiviCRM_API3_Exception + */ + public static function getMappingFieldValues($mappingID, $fieldName) { + return array_merge(CRM_Utils_Array::collect($fieldName, civicrm_api3('MappingField', 'get', ['mapping_id' => $mappingID, 'return' => $fieldName])['values'])); } /** @@ -219,7 +296,7 @@ public static function checkMapping($nameField, $mapTypeId) { * associated array of elements */ public static function getFormattedFields($smartGroupId) { - $returnFields = array(); + $returnFields = []; //get the fields from mapping table $dao = new CRM_Core_DAO_MappingField(); @@ -250,18 +327,17 @@ public static function getFormattedFields($smartGroupId) { * @param int $columnNo * @param int $blockCount * (no of blocks shown). - * @param NULL $exportMode + * @param int $exportMode */ public static function buildMappingForm(&$form, $mappingType, $mappingId, $columnNo, $blockCount, $exportMode = NULL) { - $hasLocationTypes = array(); - $hasRelationTypes = array(); - $fields = array(); + $hasLocationTypes = []; + $hasRelationTypes = []; //get the saved mapping details if ($mappingType == 'Export') { - $columnCount = array('1' => $columnNo); + $columnCount = ['1' => $columnNo]; $form->applyFilter('saveMappingName', 'trim'); //to save the current mappings @@ -273,8 +349,8 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum else { $form->assign('loadedMapping', $mappingId); - $params = array('id' => $mappingId); - $temp = array(); + $params = ['id' => $mappingId]; + $temp = []; $mappingDetails = CRM_Core_BAO_Mapping::retrieve($params, $temp); $form->assign('savedName', $mappingDetails->name); @@ -287,202 +363,57 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum $form->add('text', 'saveMappingDesc', ts('Description')); } - $form->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, array('onclick' => "showSaveDetails(this)")); - $form->addFormRule(array('CRM_Export_Form_Map', 'formRule'), $form->get('mappingTypeId')); + $form->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, ['onclick' => "showSaveDetails(this)"]); + $form->addFormRule(['CRM_Export_Form_Map', 'formRule'], $form->get('mappingTypeId')); } elseif ($mappingType == 'Search Builder') { $columnCount = $columnNo; $form->addElement('submit', 'addBlock', ts('Also include contacts where'), - array('class' => 'submit-link') + ['class' => 'submit-link'] ); } - $contactType = array('Individual', 'Household', 'Organization'); - foreach ($contactType as $value) { - if ($mappingType == 'Search Builder') { - // get multiple custom group fields in this context - $contactFields = CRM_Contact_BAO_Contact::exportableFields($value, FALSE, FALSE, FALSE, TRUE); - } - else { - $contactFields = CRM_Contact_BAO_Contact::exportableFields($value, FALSE, TRUE); - } - $contactFields = array_merge($contactFields, CRM_Contact_BAO_Query_Hook::singleton()->getFields()); - - // exclude the address options disabled in the Address Settings - $fields[$value] = CRM_Core_BAO_Address::validateAddressOptions($contactFields); - ksort($fields[$value]); - if ($mappingType == 'Export') { - $relationships = array(); - $relationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, $value, TRUE); - asort($relationshipTypes); + $contactTypes = CRM_Contact_BAO_ContactType::basicTypes(); + $fields = self::getBasicFields($mappingType); - foreach ($relationshipTypes as $key => $var) { - list($type) = explode('_', $key); - - $relationships[$key]['title'] = $var; - $relationships[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; - $relationships[$key]['export'] = TRUE; - $relationships[$key]['relationship_type_id'] = $type; - $relationships[$key]['related'] = TRUE; - $relationships[$key]['hasRelationType'] = 1; - } - - if (!empty($relationships)) { - $fields[$value] = array_merge($fields[$value], - array('related' => array('title' => ts('- related contact info -'))), - $relationships - ); - } - } - } - - //get the current employer for mapping. - if ($mappingType == 'Export') { - $fields['Individual']['current_employer_id']['title'] = ts('Current Employer ID'); - } - - // add component fields - $compArray = array(); - - //we need to unset groups, tags, notes for component export + // Unset groups, tags, notes for component export if ($exportMode != CRM_Export_Form_Select::CONTACT_EXPORT) { - foreach (array( - 'groups', - 'tags', - 'notes', - ) as $value) { - unset($fields['Individual'][$value]); - unset($fields['Household'][$value]); - unset($fields['Organization'][$value]); + foreach (array_keys($fields) as $type) { + CRM_Utils_Array::remove($fields[$type], 'groups', 'tags', 'notes'); } } if ($mappingType == 'Search Builder') { - //build the common contact fields array. - $fields['Contact'] = array(); - foreach ($fields['Individual'] as $key => $value) { - if (!empty($fields['Household'][$key]) && !empty($fields['Organization'][$key])) { + // Build the common contact fields array. + $fields['Contact'] = []; + foreach ($fields[$contactTypes[0]] as $key => $value) { + // If a field exists across all contact types, move it to the "Contact" selector + $ubiquitious = TRUE; + foreach ($contactTypes as $type) { + if (!isset($fields[$type][$key])) { + $ubiquitious = FALSE; + } + } + if ($ubiquitious) { $fields['Contact'][$key] = $value; - unset($fields['Organization'][$key], - $fields['Household'][$key], - $fields['Individual'][$key]); + foreach ($contactTypes as $type) { + unset($fields[$type][$key]); + } } } if (array_key_exists('note', $fields['Contact'])) { $noteTitle = $fields['Contact']['note']['title']; $fields['Contact']['note']['title'] = $noteTitle . ': ' . ts('Body and Subject'); - $fields['Contact']['note_body'] = array('title' => $noteTitle . ': ' . ts('Body Only'), 'name' => 'note_body'); - $fields['Contact']['note_subject'] = array( + $fields['Contact']['note_body'] = ['title' => $noteTitle . ': ' . ts('Body Only'), 'name' => 'note_body']; + $fields['Contact']['note_subject'] = [ 'title' => $noteTitle . ': ' . ts('Subject Only'), 'name' => 'note_subject', - ); - } - } - - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::CONTRIBUTE_EXPORT)) { - if (CRM_Core_Permission::access('CiviContribute')) { - $fields['Contribution'] = CRM_Contribute_BAO_Contribution::getExportableFieldsWithPseudoConstants(); - unset($fields['Contribution']['contribution_contact_id']); - $compArray['Contribution'] = ts('Contribution'); - } - } - - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT)) { - if (CRM_Core_Permission::access('CiviEvent')) { - $fields['Participant'] = CRM_Event_BAO_Participant::exportableFields(); - //get the component payment fields - if ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT) { - $componentPaymentFields = array(); - foreach (CRM_Export_BAO_Export::componentPaymentFields() as $payField => $payTitle) { - $componentPaymentFields[$payField] = array('title' => $payTitle); - } - $fields['Participant'] = array_merge($fields['Participant'], $componentPaymentFields); - } - - $compArray['Participant'] = ts('Participant'); - } - } - - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::MEMBER_EXPORT)) { - if (CRM_Core_Permission::access('CiviMember')) { - $fields['Membership'] = CRM_Member_BAO_Membership::getMembershipFields($exportMode); - unset($fields['Membership']['membership_contact_id']); - $compArray['Membership'] = ts('Membership'); - } - } - - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::PLEDGE_EXPORT)) { - if (CRM_Core_Permission::access('CiviPledge')) { - $fields['Pledge'] = CRM_Pledge_BAO_Pledge::exportableFields(); - unset($fields['Pledge']['pledge_contact_id']); - $compArray['Pledge'] = ts('Pledge'); - } - } - - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::CASE_EXPORT)) { - if (CRM_Core_Permission::access('CiviCase')) { - $fields['Case'] = CRM_Case_BAO_Case::exportableFields(); - $compArray['Case'] = ts('Case'); - - $fields['Activity'] = CRM_Activity_BAO_Activity::exportableFields('Case'); - $compArray['Activity'] = ts('Case Activity'); - - unset($fields['Case']['case_contact_id']); - } - } - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::GRANT_EXPORT)) { - if (CRM_Core_Permission::access('CiviGrant')) { - $fields['Grant'] = CRM_Grant_BAO_Grant::exportableFields(); - unset($fields['Grant']['grant_contact_id']); - if ($mappingType == 'Search Builder') { - unset($fields['Grant']['grant_type_id']); - } - $compArray['Grant'] = ts('Grant'); + ]; } } - if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::ACTIVITY_EXPORT)) { - $fields['Activity'] = CRM_Activity_BAO_Activity::exportableFields('Activity'); - $compArray['Activity'] = ts('Activity'); - } - - //Contact Sub Type For export - $contactSubTypes = array(); - $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo(); - - foreach ($subTypes as $subType => $val) { - //adding subtype specific relationships CRM-5256 - $csRelationships = array(); - - if ($mappingType == 'Export') { - $subTypeRelationshipTypes - = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, $val['parent'], - FALSE, 'label', TRUE, $subType); - - foreach ($subTypeRelationshipTypes as $key => $var) { - if (!array_key_exists($key, $fields[$val['parent']])) { - list($type) = explode('_', $key); - - $csRelationships[$key]['title'] = $var; - $csRelationships[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; - $csRelationships[$key]['export'] = TRUE; - $csRelationships[$key]['relationship_type_id'] = $type; - $csRelationships[$key]['related'] = TRUE; - $csRelationships[$key]['hasRelationType'] = 1; - } - } - } - - $fields[$subType] = $fields[$val['parent']] + $csRelationships; - - //custom fields for sub type - $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($subType); - $fields[$subType] += $subTypeFields; - - if (!empty($subTypeFields) || !empty($csRelationships)) { - $contactSubTypes[$subType] = $val['label']; - } - } + // add component fields + $compArray = self::addComponentFields($fields, $mappingType, $exportMode); foreach ($fields as $key => $value) { @@ -506,7 +437,7 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum } } - if (array_key_exists('related', $relatedMapperFields[$key])) { + if (isset($relatedMapperFields[$key]['related'])) { unset($relatedMapperFields[$key]['related']); } } @@ -521,28 +452,28 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum if ($defaultLocationType) { $defaultLocation = $locationTypes[$defaultLocationType->id]; unset($locationTypes[$defaultLocationType->id]); - $locationTypes = array($defaultLocationType->id => $defaultLocation) + $locationTypes; + $locationTypes = [$defaultLocationType->id => $defaultLocation] + $locationTypes; } - $locationTypes = array(' ' => ts('Primary')) + $locationTypes; + $locationTypes = [' ' => ts('Primary')] + $locationTypes; // since we need a hierarchical list to display contact types & subtypes, // this is what we going to display in first selector - $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, FALSE); + $contactTypeSelect = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, FALSE); if ($mappingType == 'Search Builder') { - $contactTypes = array('Contact' => ts('Contacts')) + $contactTypes; + $contactTypeSelect = ['Contact' => ts('Contacts')] + $contactTypeSelect; } - $sel1 = array('' => ts('- select record type -')) + $contactTypes + $compArray; + $sel1 = ['' => ts('- select record type -')] + $contactTypeSelect + $compArray; foreach ($sel1 as $key => $sel) { if ($key) { // sort everything BUT the contactType which is sorted separately by // an initial commit of CRM-13278 (check ksort above) - if (!in_array($key, $contactType)) { + if (!in_array($key, $contactTypes)) { asort($mapperFields[$key]); } - $sel2[$key] = array('' => ts('- select field -')) + $mapperFields[$key]; + $sel2[$key] = ['' => ts('- select field -')] + $mapperFields[$key]; } } @@ -586,11 +517,11 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum if (isset($hasRelationTypes[$k][$field])) { list($id, $first, $second) = explode('_', $field); // FIX ME: For now let's not expose custom data related to relationship - $relationshipCustomFields = array(); + $relationshipCustomFields = []; //$relationshipCustomFields = self::getRelationTypeCustomGroupData( $id ); //asort($relationshipCustomFields); - $relatedFields = array(); + $relatedFields = []; $relationshipType = new CRM_Contact_BAO_RelationshipType(); $relationshipType->id = $id; if ($relationshipType->find(TRUE)) { @@ -602,8 +533,15 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum elseif (isset($relationshipType->$target_type)) { $relatedFields = array_merge((array) $relatedMapperFields[$relationshipType->$target_type], (array) $relationshipCustomFields); } + //CRM-20672 If contact target type not set e.g. "All Contacts" relationship - present user with all field options and let them determine what they expect to work + else { + $types = CRM_Contact_BAO_ContactType::basicTypes(FALSE); + foreach ($types as $contactType => $label) { + $relatedFields = array_merge($relatedFields, (array) $relatedMapperFields[$label]); + } + $relatedFields = array_merge($relatedFields, (array) $relationshipCustomFields); + } } - $relationshipType->free(); asort($relatedFields); $sel5[$k][$field] = $relatedFields; } @@ -641,7 +579,7 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum } //special fields that have location, hack for primary location - $specialFields = array( + $specialFields = [ 'street_address', 'supplemental_address_1', 'supplemental_address_2', @@ -656,7 +594,7 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum 'phone', 'email', 'im', - ); + ]; if (isset($mappingId)) { list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingImProvider, @@ -683,94 +621,17 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum $form->set('blockCount', $form->_blockCount); $form->set('columnCount', $form->_columnCount); - $defaults = $noneArray = $nullArray = array(); + $defaults = $noneArray = $nullArray = []; for ($x = 1; $x < $blockCount; $x++) { for ($i = 0; $i < $columnCount[$x]; $i++) { - $sel = &$form->addElement('hierselect', "mapper[$x][$i]", ts('Mapper for Field %1', array(1 => $i)), NULL); + $sel = &$form->addElement('hierselect', "mapper[$x][$i]", ts('Mapper for Field %1', [1 => $i]), NULL); $jsSet = FALSE; if (isset($mappingId)) { - $locationId = isset($mappingLocation[$x][$i]) ? $mappingLocation[$x][$i] : 0; - if (isset($mappingName[$x][$i])) { - if (is_array($mapperFields[$mappingContactType[$x][$i]])) { - - if (isset($mappingRelation[$x][$i])) { - $relLocationId = isset($mappingLocation[$x][$i]) ? $mappingLocation[$x][$i] : 0; - if (!$relLocationId && in_array($mappingName[$x][$i], $specialFields)) { - $relLocationId = " "; - } - - $relPhoneType = isset($mappingPhoneType[$x][$i]) ? $mappingPhoneType[$x][$i] : NULL; - - $defaults["mapper[$x][$i]"] = array( - $mappingContactType[$x][$i], - $mappingRelation[$x][$i], - $locationId, - $phoneType, - $mappingName[$x][$i], - $relLocationId, - $relPhoneType, - ); - - if (!$locationId) { - $noneArray[] = array($x, $i, 2); - } - if (!$phoneType && !$imProvider) { - $noneArray[] = array($x, $i, 3); - } - if (!$mappingName[$x][$i]) { - $noneArray[] = array($x, $i, 4); - } - if (!$relLocationId) { - $noneArray[] = array($x, $i, 5); - } - if (!$relPhoneType) { - $noneArray[] = array($x, $i, 6); - } - $noneArray[] = array($x, $i, 2); - } - else { - $phoneType = isset($mappingPhoneType[$x][$i]) ? $mappingPhoneType[$x][$i] : NULL; - $imProvider = isset($mappingImProvider[$x][$i]) ? $mappingImProvider[$x][$i] : NULL; - if (!$locationId && in_array($mappingName[$x][$i], $specialFields)) { - $locationId = " "; - } - - $defaults["mapper[$x][$i]"] = array( - $mappingContactType[$x][$i], - $mappingName[$x][$i], - $locationId, - $phoneType, - ); - if (!$mappingName[$x][$i]) { - $noneArray[] = array($x, $i, 1); - } - if (!$locationId) { - $noneArray[] = array($x, $i, 2); - } - if (!$phoneType && !$imProvider) { - $noneArray[] = array($x, $i, 3); - } - - $noneArray[] = array($x, $i, 4); - $noneArray[] = array($x, $i, 5); - $noneArray[] = array($x, $i, 6); - } - - $jsSet = TRUE; - - if (CRM_Utils_Array::value($i, CRM_Utils_Array::value($x, $mappingOperator))) { - $defaults["operator[$x][$i]"] = CRM_Utils_Array::value($i, $mappingOperator[$x]); - } - - if (CRM_Utils_Array::value($i, CRM_Utils_Array::value($x, $mappingValue))) { - $defaults["value[$x][$i]"] = CRM_Utils_Array::value($i, $mappingValue[$x]); - } - } - } + list($mappingName, $defaults, $noneArray, $jsSet) = self::loadSavedMapping($mappingLocation, $x, $i, $mappingName, $mapperFields, $mappingContactType, $mappingRelation, $specialFields, $mappingPhoneType, $defaults, $noneArray, $mappingImProvider, $mappingOperator, $mappingValue); } //Fix for Search Builder if ($mappingType == 'Export') { @@ -785,7 +646,7 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum if (empty($formValues)) { // Incremented length for third select box(relationship type) for ($k = 1; $k < $j; $k++) { - $noneArray[] = array($x, $i, $k); + $noneArray[] = [$x, $i, $k]; } } else { @@ -795,17 +656,17 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum if (!isset($formValues['mapper'][$x][$i][$k]) || (!$formValues['mapper'][$x][$i][$k]) ) { - $noneArray[] = array($x, $i, $k); + $noneArray[] = [$x, $i, $k]; } else { - $nullArray[] = array($x, $i, $k); + $nullArray[] = [$x, $i, $k]; } } } } else { for ($k = 1; $k < $j; $k++) { - $noneArray[] = array($x, $i, $k); + $noneArray[] = [$x, $i, $k]; } } } @@ -817,23 +678,23 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum isset($formValues['mapper'][$x][$i][1]) && array_key_exists($formValues['mapper'][$x][$i][1], $relationshipTypes) ) { - $sel->setOptions(array($sel1, $sel2, $sel5, $sel6, $sel7, $sel3, $sel4)); + $sel->setOptions([$sel1, $sel2, $sel5, $sel6, $sel7, $sel3, $sel4]); } else { - $sel->setOptions(array($sel1, $sel2, $sel3, $sel4, $sel5, $sel6, $sel7)); + $sel->setOptions([$sel1, $sel2, $sel3, $sel4, $sel5, $sel6, $sel7]); } } else { - $sel->setOptions(array($sel1, $sel2, $sel3, $sel4, $sel5, $sel6, $sel7)); + $sel->setOptions([$sel1, $sel2, $sel3, $sel4, $sel5, $sel6, $sel7]); } } else { - $sel->setOptions(array($sel1, $sel2, $sel3, $sel4)); + $sel->setOptions([$sel1, $sel2, $sel3, $sel4]); } if ($mappingType == 'Search Builder') { //CRM -2292, restricted array set - $operatorArray = array('' => ts('-operator-')) + CRM_Core_SelectValues::getSearchBuilderOperators(); + $operatorArray = ['' => ts('-operator-')] + CRM_Core_SelectValues::getSearchBuilderOperators(); $form->add('select', "operator[$x][$i]", '', $operatorArray); $form->add('text', "value[$x][$i]", ''); @@ -847,7 +708,7 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum $title = ts('Select more fields'); } - $form->addElement('submit', "addMore[$x]", $title, array('class' => 'submit-link')); + $form->addElement('submit', "addMore[$x]", $title, ['class' => 'submit-link']); } //end of block for @@ -855,8 +716,8 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum $formName = "document." . (($mappingType == 'Export') ? 'Map' : 'Builder'); if (!empty($nullArray)) { $js .= "var nullArray = ["; - $elements = array(); - $seen = array(); + $elements = []; + $seen = []; foreach ($nullArray as $element) { $key = "{$element[0]}, {$element[1]}, {$element[2]}"; if (!isset($seen[$key])) { @@ -876,8 +737,8 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum } if (!empty($noneArray)) { $js .= "var noneArray = ["; - $elements = array(); - $seen = array(); + $elements = []; + $seen = []; foreach ($noneArray as $element) { $key = "{$element[0]}, {$element[1]}, {$element[2]}"; if (!isset($seen[$key])) { @@ -905,6 +766,347 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum $form->setDefaultAction('refresh'); } + /** + * @param string $mappingType + * @return array + */ + public static function getBasicFields($mappingType) { + $contactTypes = CRM_Contact_BAO_ContactType::basicTypes(); + $fields = []; + foreach ($contactTypes as $contactType) { + if ($mappingType == 'Search Builder') { + // Get multiple custom group fields in this context + $contactFields = CRM_Contact_BAO_Contact::exportableFields($contactType, FALSE, FALSE, FALSE, TRUE); + } + else { + $contactFields = CRM_Contact_BAO_Contact::exportableFields($contactType, FALSE, TRUE); + } + $contactFields = array_merge($contactFields, CRM_Contact_BAO_Query_Hook::singleton()->getFields()); + + // Exclude the address options disabled in the Address Settings + $fields[$contactType] = CRM_Core_BAO_Address::validateAddressOptions($contactFields); + ksort($fields[$contactType]); + if ($mappingType == 'Export') { + $relationships = []; + $relationshipTypes = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, $contactType); + asort($relationshipTypes); + + foreach ($relationshipTypes as $key => $var) { + list($type) = explode('_', $key); + + $relationships[$key]['title'] = $var; + $relationships[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; + $relationships[$key]['export'] = TRUE; + $relationships[$key]['relationship_type_id'] = $type; + $relationships[$key]['related'] = TRUE; + $relationships[$key]['hasRelationType'] = 1; + } + + if (!empty($relationships)) { + $fields[$contactType] = array_merge($fields[$contactType], + ['related' => ['title' => ts('- related contact info -')]], + $relationships + ); + } + } + } + + // Get the current employer for mapping. + if ($mappingType == 'Export') { + $fields['Individual']['current_employer_id']['title'] = ts('Current Employer ID'); + } + + // Contact Sub Type For export + $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo(); + foreach ($subTypes as $subType => $info) { + //adding subtype specific relationships CRM-5256 + $csRelationships = []; + + if ($mappingType == 'Export') { + $subTypeRelationshipTypes + = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, $info['parent'], + FALSE, 'label', TRUE, $subType); + + foreach ($subTypeRelationshipTypes as $key => $var) { + if (!array_key_exists($key, $fields[$info['parent']])) { + list($type) = explode('_', $key); + + $csRelationships[$key]['title'] = $var; + $csRelationships[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/'; + $csRelationships[$key]['export'] = TRUE; + $csRelationships[$key]['relationship_type_id'] = $type; + $csRelationships[$key]['related'] = TRUE; + $csRelationships[$key]['hasRelationType'] = 1; + } + } + } + + $fields[$subType] = $fields[$info['parent']] + $csRelationships; + + //custom fields for sub type + $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($subType); + $fields[$subType] += $subTypeFields; + } + + return $fields; + } + + /** + * Adds component fields to the export fields array; returns list of components. + * + * @param array $fields + * @param string $mappingType + * @param int $exportMode + * @return array + */ + public static function addComponentFields(&$fields, $mappingType, $exportMode) { + $compArray = []; + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::CONTRIBUTE_EXPORT)) { + if (CRM_Core_Permission::access('CiviContribute')) { + $fields['Contribution'] = CRM_Core_DAO::getExportableFieldsWithPseudoConstants('CRM_Contribute_BAO_Contribution'); + unset($fields['Contribution']['contribution_contact_id']); + $compArray['Contribution'] = ts('Contribution'); + } + } + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT)) { + if (CRM_Core_Permission::access('CiviEvent')) { + $fields['Participant'] = CRM_Event_BAO_Participant::exportableFields(); + //get the component payment fields + // @todo - review this - inconsistent with other entities & hacky. + if ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT) { + $componentPaymentFields = []; + foreach ([ + 'componentPaymentField_total_amount' => ts('Total Amount'), + 'componentPaymentField_contribution_status' => ts('Contribution Status'), + 'componentPaymentField_received_date' => ts('Date Received'), + 'componentPaymentField_payment_instrument' => ts('Payment Method'), + 'componentPaymentField_transaction_id' => ts('Transaction ID'), + ] as $payField => $payTitle) { + $componentPaymentFields[$payField] = ['title' => $payTitle]; + } + $fields['Participant'] = array_merge($fields['Participant'], $componentPaymentFields); + } + + $compArray['Participant'] = ts('Participant'); + } + } + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::MEMBER_EXPORT)) { + if (CRM_Core_Permission::access('CiviMember')) { + $fields['Membership'] = CRM_Member_BAO_Membership::getMembershipFields($exportMode); + unset($fields['Membership']['membership_contact_id']); + $compArray['Membership'] = ts('Membership'); + } + } + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::PLEDGE_EXPORT)) { + if (CRM_Core_Permission::access('CiviPledge')) { + $fields['Pledge'] = CRM_Pledge_BAO_Pledge::exportableFields(); + unset($fields['Pledge']['pledge_contact_id']); + $compArray['Pledge'] = ts('Pledge'); + } + } + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::CASE_EXPORT)) { + if (CRM_Core_Permission::access('CiviCase')) { + $fields['Case'] = CRM_Case_BAO_Case::exportableFields(); + $compArray['Case'] = ts('Case'); + + $fields['Activity'] = CRM_Activity_BAO_Activity::exportableFields('Case'); + $compArray['Activity'] = ts('Case Activity'); + + unset($fields['Case']['case_contact_id']); + } + } + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::GRANT_EXPORT)) { + if (CRM_Core_Permission::access('CiviGrant')) { + $fields['Grant'] = CRM_Grant_BAO_Grant::exportableFields(); + unset($fields['Grant']['grant_contact_id']); + if ($mappingType == 'Search Builder') { + unset($fields['Grant']['grant_type_id']); + } + $compArray['Grant'] = ts('Grant'); + } + } + + if (($mappingType == 'Search Builder') || ($exportMode == CRM_Export_Form_Select::ACTIVITY_EXPORT)) { + $fields['Activity'] = CRM_Activity_BAO_Activity::exportableFields('Activity'); + $compArray['Activity'] = ts('Activity'); + } + + return $compArray; + } + + /** + * Get the parameters for a mapping field in a saveable format from the quickform mapping format. + * + * @param array $defaults + * @param array $v + * + * @return array + */ + public static function getMappingParams($defaults, $v) { + $locationTypeId = NULL; + $saveMappingFields = $defaults; + + $saveMappingFields['name'] = CRM_Utils_Array::value('1', $v); + $saveMappingFields['contact_type'] = CRM_Utils_Array::value('0', $v); + $locationId = CRM_Utils_Array::value('2', $v); + $saveMappingFields['location_type_id'] = is_numeric($locationId) ? $locationId : NULL; + + if ($v[1] == 'phone') { + $saveMappingFields['phone_type_id'] = CRM_Utils_Array::value('3', $v); + } + elseif ($v[1] == 'im') { + $saveMappingFields['im_provider_id'] = CRM_Utils_Array::value('3', $v); + } + + // Handle mapping for 'related contact' fields + if (count(explode('_', CRM_Utils_Array::value('1', $v))) > 2) { + list($id, $first, $second) = explode('_', CRM_Utils_Array::value('1', $v)); + if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) { + + if (!empty($v['2'])) { + $saveMappingFields['name'] = CRM_Utils_Array::value('2', $v); + } + elseif (!empty($v['4'])) { + $saveMappingFields['name'] = CRM_Utils_Array::value('4', $v); + } + + if (is_numeric(CRM_Utils_Array::value('3', $v))) { + $locationTypeId = CRM_Utils_Array::value('3', $v); + } + elseif (is_numeric(CRM_Utils_Array::value('5', $v))) { + $locationTypeId = CRM_Utils_Array::value('5', $v); + } + + if (is_numeric(CRM_Utils_Array::value('4', $v))) { + if ($saveMappingFields['name'] === 'im') { + $saveMappingFields['im_provider_id'] = $v[4]; + } + else { + $saveMappingFields['phone_type_id'] = CRM_Utils_Array::value('4', $v); + } + } + elseif (is_numeric(CRM_Utils_Array::value('6', $v))) { + $saveMappingFields['phone_type_id'] = CRM_Utils_Array::value('6', $v); + } + + $saveMappingFields['location_type_id'] = is_numeric($locationTypeId) ? $locationTypeId : NULL; + $saveMappingFields['relationship_type_id'] = $id; + $saveMappingFields['relationship_direction'] = "{$first}_{$second}"; + } + } + + return $saveMappingFields; + } + + /** + * Load saved mapping. + * + * @param $mappingLocation + * @param int $x + * @param int $i + * @param $mappingName + * @param $mapperFields + * @param $mappingContactType + * @param $mappingRelation + * @param array $specialFields + * @param $mappingPhoneType + * @param $phoneType + * @param array $defaults + * @param array $noneArray + * @param $imProvider + * @param $mappingImProvider + * @param $mappingOperator + * @param $mappingValue + * + * @return array + */ + protected static function loadSavedMapping($mappingLocation, int $x, int $i, $mappingName, $mapperFields, $mappingContactType, $mappingRelation, array $specialFields, $mappingPhoneType, array $defaults, array $noneArray, $mappingImProvider, $mappingOperator, $mappingValue) { + $locationId = isset($mappingLocation[$x][$i]) ? $mappingLocation[$x][$i] : 0; + if (isset($mappingName[$x][$i])) { + if (is_array($mapperFields[$mappingContactType[$x][$i]])) { + + if (isset($mappingRelation[$x][$i])) { + $relLocationId = isset($mappingLocation[$x][$i]) ? $mappingLocation[$x][$i] : 0; + if (!$relLocationId && in_array($mappingName[$x][$i], $specialFields)) { + $relLocationId = " "; + } + + $relPhoneType = isset($mappingPhoneType[$x][$i]) ? $mappingPhoneType[$x][$i] : NULL; + + $defaults["mapper[$x][$i]"] = [ + $mappingContactType[$x][$i], + $mappingRelation[$x][$i], + $locationId, + $phoneType, + $mappingName[$x][$i], + $relLocationId, + $relPhoneType, + ]; + + if (!$locationId) { + $noneArray[] = [$x, $i, 2]; + } + if (!$phoneType && !$imProvider) { + $noneArray[] = [$x, $i, 3]; + } + if (!$mappingName[$x][$i]) { + $noneArray[] = [$x, $i, 4]; + } + if (!$relLocationId) { + $noneArray[] = [$x, $i, 5]; + } + if (!$relPhoneType) { + $noneArray[] = [$x, $i, 6]; + } + $noneArray[] = [$x, $i, 2]; + } + else { + $phoneType = isset($mappingPhoneType[$x][$i]) ? $mappingPhoneType[$x][$i] : NULL; + $imProvider = isset($mappingImProvider[$x][$i]) ? $mappingImProvider[$x][$i] : NULL; + if (!$locationId && in_array($mappingName[$x][$i], $specialFields)) { + $locationId = " "; + } + + $defaults["mapper[$x][$i]"] = [ + $mappingContactType[$x][$i], + $mappingName[$x][$i], + $locationId, + $phoneType, + ]; + if (!$mappingName[$x][$i]) { + $noneArray[] = [$x, $i, 1]; + } + if (!$locationId) { + $noneArray[] = [$x, $i, 2]; + } + if (!$phoneType && !$imProvider) { + $noneArray[] = [$x, $i, 3]; + } + + $noneArray[] = [$x, $i, 4]; + $noneArray[] = [$x, $i, 5]; + $noneArray[] = [$x, $i, 6]; + } + + $jsSet = TRUE; + + if (CRM_Utils_Array::value($i, CRM_Utils_Array::value($x, $mappingOperator))) { + $defaults["operator[$x][$i]"] = CRM_Utils_Array::value($i, $mappingOperator[$x]); + } + + if (CRM_Utils_Array::value($i, CRM_Utils_Array::value($x, $mappingValue))) { + $defaults["value[$x][$i]"] = CRM_Utils_Array::value($i, $mappingValue[$x]); + } + } + } + return [$mappingName, $defaults, $noneArray, $jsSet]; + } + /** * Function returns all custom fields with group title and * field label @@ -918,14 +1120,13 @@ public static function buildMappingForm(&$form, $mappingType, $mappingId, $colum public function getRelationTypeCustomGroupData($relationshipTypeId) { $customFields = CRM_Core_BAO_CustomField::getFields('Relationship', NULL, NULL, $relationshipTypeId, NULL, NULL); - $groupTitle = array(); + $groupTitle = []; foreach ($customFields as $krelation => $vrelation) { $groupTitle[$vrelation['label']] = $vrelation['groupTitle'] . '...' . $vrelation['label']; } return $groupTitle; } - /** * Function returns all Custom group Names. * @@ -940,9 +1141,7 @@ public static function getCustomGroupName($customfieldId) { $customGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customFieldId, 'custom_group_id'); $customGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'title'); - if (strlen($customGroupName) > 13) { - $customGroupName = substr($customGroupName, 0, 10) . '...'; - } + $customGroupName = CRM_Utils_String::ellipsify($customGroupName, 13); return $customGroupName; } @@ -961,20 +1160,20 @@ public static function getCustomGroupName($customfieldId) { * formatted associated array of elements */ public static function formattedFields(&$params, $row = FALSE) { - $fields = array(); + $fields = []; if (empty($params) || !isset($params['mapper'])) { return $fields; } - $types = array('Individual', 'Organization', 'Household'); + $types = ['Individual', 'Organization', 'Household']; foreach ($params['mapper'] as $key => $value) { $contactType = NULL; foreach ($value as $k => $v) { if (in_array($v[0], $types)) { if ($contactType && $contactType != $v[0]) { CRM_Core_Error::fatal(ts("Cannot have two clauses with different types: %1, %2", - array(1 => $contactType, 2 => $v[0]) + [1 => $contactType, 2 => $v[0]] )); } $contactType = $v[0]; @@ -1004,7 +1203,7 @@ public static function formattedFields(&$params, $row = FALSE) { // CRM-14983: verify if values are comma separated convert to array if (!is_array($value) && strstr($params['operator'][$key][$k], 'IN')) { $value = explode(',', $value); - $value = array($params['operator'][$key][$k] => $value); + $value = [$params['operator'][$key][$k] => $value]; } // CRM-19081 Fix legacy StateProvince Field Values. // These derive from smart groups created using search builder under older @@ -1014,45 +1213,45 @@ public static function formattedFields(&$params, $row = FALSE) { } if ($row) { - $fields[] = array( + $fields[] = [ $fldName, $params['operator'][$key][$k], $value, $key, $k, - ); + ]; } else { - $fields[] = array( + $fields[] = [ $fldName, $params['operator'][$key][$k], $value, $key, 0, - ); + ]; } } } if ($contactType) { - $fields[] = array( + $fields[] = [ 'contact_type', '=', $contactType, $key, 0, - ); + ]; } } //add sortByCharacter values if (isset($params['sortByCharacter'])) { - $fields[] = array( + $fields[] = [ 'sortByCharacter', '=', $params['sortByCharacter'], 0, 0, - ); + ]; } return $fields; } @@ -1063,17 +1262,17 @@ public static function formattedFields(&$params, $row = FALSE) { * @return array */ public static function &returnProperties(&$params) { - $fields = array( + $fields = [ 'contact_type' => 1, 'contact_sub_type' => 1, 'sort_name' => 1, - ); + ]; if (empty($params) || empty($params['mapper'])) { return $fields; } - $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'); + $locationTypes = CRM_Core_DAO_Address::buildOptions('location_type_id', 'validate'); foreach ($params['mapper'] as $key => $value) { foreach ($value as $k => $v) { if (isset($v[1])) { @@ -1083,13 +1282,13 @@ public static function &returnProperties(&$params) { if (isset($v[2]) && is_numeric($v[2])) { if (!array_key_exists('location', $fields)) { - $fields['location'] = array(); + $fields['location'] = []; } // make sure that we have a location fields and a location type for this $locationName = $locationTypes[$v[2]]; if (!array_key_exists($locationName, $fields['location'])) { - $fields['location'][$locationName] = array(); + $fields['location'][$locationName] = []; $fields['location'][$locationName]['location_type'] = $v[2]; } @@ -1126,7 +1325,7 @@ public static function &returnProperties(&$params) { * * @return NULL */ - public static function saveMappingFields(&$params, $mappingId) { + public static function saveMappingFields($params, $mappingId) { //delete mapping fields records for existing mapping $mappingFields = new CRM_Core_DAO_MappingField(); $mappingFields->mapping_id = $mappingId; @@ -1142,65 +1341,18 @@ public static function saveMappingFields(&$params, $mappingId) { foreach ($value as $k => $v) { if (!empty($v['1'])) { - $saveMappingFields = new CRM_Core_DAO_MappingField(); - - $saveMappingFields->mapping_id = $mappingId; - $saveMappingFields->name = CRM_Utils_Array::value('1', $v); - $saveMappingFields->contact_type = CRM_Utils_Array::value('0', $v); - $locationId = CRM_Utils_Array::value('2', $v); - $saveMappingFields->location_type_id = is_numeric($locationId) ? $locationId : NULL; - - if ($v[1] == 'phone') { - $saveMappingFields->phone_type_id = CRM_Utils_Array::value('3', $v); - } - elseif ($v[1] == 'im') { - $saveMappingFields->im_provider_id = CRM_Utils_Array::value('3', $v); - } - - if (!empty($params['operator'])) { - $saveMappingFields->operator = CRM_Utils_Array::value($k, $params['operator'][$key]); - } - if (!empty($params['value'])) { - $saveMappingFields->value = CRM_Utils_Array::value($k, $params['value'][$key]); - } - // Handle mapping for 'related contact' fields - if (count(explode('_', CRM_Utils_Array::value('1', $v))) > 2) { - list($id, $first, $second) = explode('_', CRM_Utils_Array::value('1', $v)); - if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) { - - if (!empty($v['2'])) { - $saveMappingFields->name = CRM_Utils_Array::value('2', $v); - } - elseif (!empty($v['4'])) { - $saveMappingFields->name = CRM_Utils_Array::value('4', $v); - } - - if (is_numeric(CRM_Utils_Array::value('3', $v))) { - $locationTypeid = CRM_Utils_Array::value('3', $v); - } - elseif (is_numeric(CRM_Utils_Array::value('5', $v))) { - $locationTypeid = CRM_Utils_Array::value('5', $v); - } - - if (is_numeric(CRM_Utils_Array::value('4', $v))) { - $phoneTypeid = CRM_Utils_Array::value('4', $v); - } - elseif (is_numeric(CRM_Utils_Array::value('6', $v))) { - $phoneTypeid = CRM_Utils_Array::value('6', $v); - } - - $saveMappingFields->location_type_id = is_numeric($locationTypeid) ? $locationTypeid : NULL; - $saveMappingFields->phone_type_id = is_numeric($phoneTypeid) ? $phoneTypeid : NULL; - $saveMappingFields->relationship_type_id = $id; - $saveMappingFields->relationship_direction = "{$first}_{$second}"; - } - } - - $saveMappingFields->grouping = $key; - $saveMappingFields->column_number = $colCnt; - $saveMappingFields->save(); + $saveMappingParams = self::getMappingParams( + [ + 'mapping_id' => $mappingId, + 'grouping' => $key, + 'operator' => $params['operator'][$key][$k] ?? NULL, + 'value' => $params['value'][$key][$k] ?? NULL, + 'column_number' => $colCnt, + ], $v); + $saveMappingField = new CRM_Core_DAO_MappingField(); + $saveMappingField->copyValues($saveMappingParams, TRUE); + $saveMappingField->save(); $colCnt++; - $locationTypeid = $phoneTypeid = NULL; } } } diff --git a/CRM/Core/BAO/MessageTemplate.php b/CRM/Core/BAO/MessageTemplate.php index 2b87046c0eb8..3c28a5a4aea4 100644 --- a/CRM/Core/BAO/MessageTemplate.php +++ b/CRM/Core/BAO/MessageTemplate.php @@ -1,9 +1,9 @@ $params['id']]); + if (!empty($details['workflow_id'])) { + if (!CRM_Core_Permission::check('edit system workflow message templates')) { + throw new \Civi\API\Exception\UnauthorizedException(ts('%1', [1 => $systemWorkflowPermissionDeniedMessage])); + } + } + elseif (!CRM_Core_Permission::check('edit user-driven message templates')) { + throw new \Civi\API\Exception\UnauthorizedException(ts('%1', [1 => $userWorkflowPermissionDeniedMessage])); + } + } + else { + if (!empty($params['workflow_id']) && !CRM_Core_Permission::check('edit system workflow message templates')) { + throw new \Civi\API\Exception\UnauthorizedException(ts('%1', [1 => $systemWorkflowPermissionDeniedMessage])); + } + elseif (!CRM_Core_Permission::check('edit user-driven message templates')) { + throw new \Civi\API\Exception\UnauthorizedException(ts('%1', [1 => $userWorkflowPermissionDeniedMessage])); + } + } + } + } $hook = empty($params['id']) ? 'create' : 'edit'; CRM_Utils_Hook::pre($hook, 'MessageTemplate', CRM_Utils_Array::value('id', $params), $params); @@ -130,7 +157,7 @@ public static function del($messageTemplatesID) { SET msg_template_id = NULL WHERE msg_template_id = %1"; - $params = array(1 => array($messageTemplatesID, 'Integer')); + $params = [1 => [$messageTemplatesID, 'Integer']]; CRM_Core_DAO::executeQuery($query, $params); $messageTemplates = new CRM_Core_DAO_MessageTemplate(); @@ -150,7 +177,7 @@ public static function del($messageTemplatesID) { * @return object */ public static function getMessageTemplates($all = TRUE, $isSMS = FALSE) { - $msgTpls = array(); + $msgTpls = []; $messageTemplates = new CRM_Core_DAO_MessageTemplate(); $messageTemplates->is_active = 1; @@ -182,7 +209,7 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $domain = CRM_Core_BAO_Domain::getDomain(); $result = NULL; - $hookTokens = array(); + $hookTokens = []; if ($messageTemplates->find(TRUE)) { $body_text = $messageTemplates->msg_text; @@ -192,7 +219,7 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $body_text = CRM_Utils_String::htmlToText($body_html); } - $params = array(array('contact_id', '=', $contactId, 0, 0)); + $params = [['contact_id', '=', $contactId, 0, 0]]; list($contact, $_) = CRM_Contact_BAO_Query::apiQuery($params); //CRM-4524 @@ -210,13 +237,13 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro CRM_Utils_Token::getTokens($body_subject)); // get replacement text for these tokens - $returnProperties = array("preferred_mail_format" => 1); + $returnProperties = ["preferred_mail_format" => 1]; if (isset($tokens['contact'])) { foreach ($tokens['contact'] as $key => $value) { $returnProperties[$value] = 1; } } - list($details) = CRM_Utils_Token::getTokenDetails(array($contactId), + list($details) = CRM_Utils_Token::getTokenDetails([$contactId], $returnProperties, NULL, NULL, FALSE, $tokens, @@ -224,12 +251,12 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $contact = reset($details); // call token hook - $hookTokens = array(); + $hookTokens = []; CRM_Utils_Hook::tokens($hookTokens); $categories = array_keys($hookTokens); // do replacements in text and html body - $type = array('html', 'text'); + $type = ['html', 'text']; foreach ($type as $key => $value) { $bodyType = "body_{$value}"; if ($$bodyType) { @@ -244,10 +271,10 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $text = $body_text; $smarty = CRM_Core_Smarty::singleton(); - foreach (array( - 'text', - 'html', - ) as $elem) { + foreach ([ + 'text', + 'html', + ] as $elem) { $$elem = $smarty->fetch("string:{$$elem}"); } @@ -260,13 +287,13 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $messageSubject = $smarty->fetch("string:{$messageSubject}"); // set up the parameters for CRM_Utils_Mail::send - $mailParams = array( + $mailParams = [ 'groupName' => 'Scheduled Reminder Sender', 'from' => $from, 'toName' => $contact['display_name'], 'toEmail' => $email, 'subject' => $messageSubject, - ); + ]; if (!$html || $contact['preferred_mail_format'] == 'Text' || $contact['preferred_mail_format'] == 'Both' ) { @@ -283,8 +310,6 @@ public static function sendReminder($contactId, $email, $messageTemplateID, $fro $result = CRM_Utils_Mail::send($mailParams); } - $messageTemplates->free(); - return $result; } @@ -299,7 +324,7 @@ public static function revert($id) { $diverted->find(1); if ($diverted->N != 1) { - CRM_Core_Error::fatal(ts('Did not find a message template with id of %1.', array(1 => $id))); + CRM_Core_Error::fatal(ts('Did not find a message template with id of %1.', [1 => $id])); } $orig = new CRM_Core_BAO_MessageTemplate(); @@ -308,7 +333,7 @@ public static function revert($id) { $orig->find(1); if ($orig->N != 1) { - CRM_Core_Error::fatal(ts('Message template with id of %1 does not have a default to revert to.', array(1 => $id))); + CRM_Core_Error::fatal(ts('Message template with id of %1 does not have a default to revert to.', [1 => $id])); } $diverted->msg_subject = $orig->msg_subject; @@ -328,7 +353,7 @@ public static function revert($id) { * Array of four parameters: a boolean whether the email was sent, and the subject, text and HTML templates */ public static function sendTemplate($params) { - $defaults = array( + $defaults = [ // option group name of the template 'groupName' => NULL, // option value name of the template @@ -338,7 +363,7 @@ public static function sendTemplate($params) { // contact id if contact tokens are to be replaced 'contactId' => NULL, // additional template params (other than the ones already set in the template singleton) - 'tplParams' => array(), + 'tplParams' => [], // the From: header 'from' => NULL, // the recipient’s name @@ -357,9 +382,14 @@ public static function sendTemplate($params) { 'isTest' => FALSE, // filename of optional PDF version to add as attachment (do not include path) 'PDFFilename' => NULL, - ); + ]; $params = array_merge($defaults, $params); + // Core#644 - handle Email ID passed as "From". + if (isset($params['from'])) { + $params['from'] = CRM_Utils_Mail::formatFromAddress($params['from']); + } + CRM_Utils_Hook::alterMailParams($params, 'messageTemplate'); if ((!$params['groupName'] || @@ -375,7 +405,7 @@ public static function sendTemplate($params) { $query = 'SELECT msg_subject subject, msg_text text, msg_html html, pdf_format_id format FROM civicrm_msg_template mt WHERE mt.id = %1 AND mt.is_default = 1'; - $sqlParams = array(1 => array($params['messageTemplateID'], 'String')); + $sqlParams = [1 => [$params['messageTemplateID'], 'String']]; } else { // fetch the three elements from the db based on option_group and option_value names @@ -384,30 +414,32 @@ public static function sendTemplate($params) { JOIN civicrm_option_value ov ON workflow_id = ov.id JOIN civicrm_option_group og ON ov.option_group_id = og.id WHERE og.name = %1 AND ov.name = %2 AND mt.is_default = 1'; - $sqlParams = array(1 => array($params['groupName'], 'String'), 2 => array($params['valueName'], 'String')); + $sqlParams = [1 => [$params['groupName'], 'String'], 2 => [$params['valueName'], 'String']]; } $dao = CRM_Core_DAO::executeQuery($query, $sqlParams); $dao->fetch(); if (!$dao->N) { if ($params['messageTemplateID']) { - CRM_Core_Error::fatal(ts('No such message template: id=%1.', array(1 => $params['messageTemplateID']))); + CRM_Core_Error::fatal(ts('No such message template: id=%1.', [1 => $params['messageTemplateID']])); } else { - CRM_Core_Error::fatal(ts('No such message template: option group %1, option value %2.', array( - 1 => $params['groupName'], - 2 => $params['valueName'], - ))); + CRM_Core_Error::fatal(ts('No such message template: option group %1, option value %2.', [ + 1 => $params['groupName'], + 2 => $params['valueName'], + ])); } } - $mailContent = array( + $mailContent = [ 'subject' => $dao->subject, 'text' => $dao->text, 'html' => $dao->html, 'format' => $dao->format, - ); - $dao->free(); + 'groupName' => $params['groupName'], + 'valueName' => $params['valueName'], + 'messageTemplateID' => $params['messageTemplateID'], + ]; CRM_Utils_Hook::alterMailContent($mailContent); @@ -424,12 +456,11 @@ public static function sendTemplate($params) { $mailContent['subject'] = $testDao->subject . $mailContent['subject']; $mailContent['text'] = $testDao->text . $mailContent['text']; $mailContent['html'] = preg_replace('/html}", $mailContent['html']); - $testDao->free(); } // replace tokens in the three elements (in subject as if it was the text body) $domain = CRM_Core_BAO_Domain::getDomain(); - $hookTokens = array(); + $hookTokens = []; $mailing = new CRM_Mailing_BAO_Mailing(); $mailing->subject = $mailContent['subject']; $mailing->body_text = $mailContent['text']; @@ -441,8 +472,8 @@ public static function sendTemplate($params) { $contactID = CRM_Utils_Array::value('contactId', $params); if ($contactID) { - $contactParams = array('contact_id' => $contactID); - $returnProperties = array(); + $contactParams = ['contact_id' => $contactID]; + $returnProperties = []; if (isset($tokens['subject']['contact'])) { foreach ($tokens['subject']['contact'] as $name) { @@ -485,9 +516,9 @@ public static function sendTemplate($params) { $mailContent['text'] = CRM_Utils_Token::replaceContactTokens($mailContent['text'], $contact, FALSE, $tokens['text'], FALSE, TRUE); $mailContent['html'] = CRM_Utils_Token::replaceContactTokens($mailContent['html'], $contact, FALSE, $tokens['html'], FALSE, TRUE); - $contactArray = array($contactID => $contact); + $contactArray = [$contactID => $contact]; CRM_Utils_Hook::tokenValues($contactArray, - array($contactID), + [$contactID], NULL, CRM_Utils_Token::flattenTokens($tokens), // we should consider adding groupName and valueName here @@ -508,11 +539,11 @@ public static function sendTemplate($params) { foreach ($params['tplParams'] as $name => $value) { $smarty->assign($name, $value); } - foreach (array( + foreach ([ 'subject', 'text', 'html', - ) as $elem) { + ] as $elem) { $mailContent[$elem] = $smarty->fetch("string:{$mailContent[$elem]}"); } @@ -525,7 +556,7 @@ public static function sendTemplate($params) { $params['html'] = $mailContent['html']; if ($params['toEmail']) { - $contactParams = array(array('email', 'LIKE', $params['toEmail'], 0, 1)); + $contactParams = [['email', 'LIKE', $params['toEmail'], 0, 1]]; list($contact, $_) = CRM_Contact_BAO_Query::apiQuery($contactParams); $prefs = array_pop($contact); @@ -542,7 +573,7 @@ public static function sendTemplate($params) { if (isset($params['isEmailPdf']) && $params['isEmailPdf'] == 1) { $pdfHtml = CRM_Contribute_BAO_ContributionPage::addInvoicePdfToEmail($params['contributionId'], $params['contactId']); if (empty($params['attachments'])) { - $params['attachments'] = array(); + $params['attachments'] = []; } $params['attachments'][] = CRM_Utils_Mail::appendPDF('Invoice.pdf', $pdfHtml, $mailContent['format']); } @@ -552,7 +583,7 @@ public static function sendTemplate($params) { $params['html'] ) { if (empty($params['attachments'])) { - $params['attachments'] = array(); + $params['attachments'] = []; } $params['attachments'][] = CRM_Utils_Mail::appendPDF($params['PDFFilename'], $params['html'], $mailContent['format']); if (isset($params['tplParams']['email_comment'])) { @@ -568,7 +599,7 @@ public static function sendTemplate($params) { } } - return array($sent, $mailContent['subject'], $mailContent['text'], $mailContent['html']); + return [$sent, $mailContent['subject'], $mailContent['text'], $mailContent['html']]; } } diff --git a/CRM/Core/BAO/Navigation.php b/CRM/Core/BAO/Navigation.php index 32f18b86806e..44a536a424d3 100644 --- a/CRM/Core/BAO/Navigation.php +++ b/CRM/Core/BAO/Navigation.php @@ -1,9 +1,9 @@ domain_id = CRM_Core_Config::domainID(); - $menu->find(); - - while ($menu->fetch()) { - if ($menu->title) { - $menus[$menu->path] = $menu->title; - } - } - return $menus; - } - /** * Add/update navigation record. * @@ -91,6 +70,7 @@ public static function add(&$params) { if (empty($params['id'])) { $params['is_active'] = CRM_Utils_Array::value('is_active', $params, FALSE); $params['has_separator'] = CRM_Utils_Array::value('has_separator', $params, FALSE); + $params['domain_id'] = CRM_Utils_Array::value('domain_id', $params, CRM_Core_Config::domainID()); } if (!isset($params['id']) || @@ -114,8 +94,6 @@ public static function add(&$params) { $navigation->copyValues($params); - $navigation->domain_id = CRM_Core_Config::domainID(); - $navigation->save(); return $navigation; } @@ -181,22 +159,22 @@ public static function calculateWeight($parentID = NULL, $menuID = NULL) { * returns associated array */ public static function getNavigationList() { - $cacheKeyString = "navigationList"; + $cacheKeyString = "navigationList_" . CRM_Core_Config::domainID(); $whereClause = ''; $config = CRM_Core_Config::singleton(); // check if we can retrieve from database cache - $navigations = CRM_Core_BAO_Cache::getItem('navigation', $cacheKeyString); + $navigations = Civi::cache('navigation')->get($cacheKeyString); if (!$navigations) { $domainID = CRM_Core_Config::domainID(); $query = " SELECT id, label, parent_id, weight, is_active, name -FROM civicrm_navigation WHERE domain_id = $domainID {$whereClause} ORDER BY parent_id, weight ASC"; +FROM civicrm_navigation WHERE domain_id = $domainID"; $result = CRM_Core_DAO::executeQuery($query); - $pidGroups = array(); + $pidGroups = []; while ($result->fetch()) { $pidGroups[$result->parent_id][$result->label] = $result->id; } @@ -205,10 +183,10 @@ public static function getNavigationList() { $pidGroups[''][$label] = self::_getNavigationValue($val, $pidGroups); } - $navigations = array(); + $navigations = []; self::_getNavigationLabel($pidGroups[''], $navigations); - CRM_Core_BAO_Cache::setItem($navigations, 'navigation', $cacheKeyString); + Civi::cache('navigation')->set($cacheKeyString, $navigations); } return $navigations; } @@ -229,7 +207,7 @@ public static function _getNavigationLabel($list, &$navigations, $separator = '' if ($label == 'navigation_id') { continue; } - $translatedLabel = $i18n->crm_translate($label, array('context' => 'menu')); + $translatedLabel = $i18n->crm_translate($label, ['context' => 'menu']); $navigations[is_array($val) ? $val['navigation_id'] : $val] = "{$separator}{$translatedLabel}"; if (is_array($val)) { self::_getNavigationLabel($val, $navigations, $separator . '    '); @@ -249,7 +227,7 @@ public static function _getNavigationLabel($list, &$navigations, $separator = '' */ public static function _getNavigationValue($val, &$pidGroups) { if (array_key_exists($val, $pidGroups)) { - $list = array('navigation_id' => $val); + $list = ['navigation_id' => $val]; foreach ($pidGroups[$val] as $label => $id) { $list[$label] = self::_getNavigationValue($id, $pidGroups); } @@ -269,30 +247,29 @@ public static function _getNavigationValue($val, &$pidGroups) { */ public static function buildNavigationTree() { $domainID = CRM_Core_Config::domainID(); - $navigationTree = array(); - - // get the list of menus - $query = " -SELECT id, label, url, permission, permission_operator, has_separator, parent_id, is_active, name -FROM civicrm_navigation -WHERE domain_id = $domainID -ORDER BY parent_id, weight"; - - $navigation = CRM_Core_DAO::executeQuery($query); - while ($navigation->fetch()) { - $navigationTree[$navigation->id] = array( - 'attributes' => array( - 'label' => $navigation->label, - 'name' => $navigation->name, - 'url' => $navigation->url, - 'permission' => $navigation->permission, - 'operator' => $navigation->permission_operator, - 'separator' => $navigation->has_separator, - 'parentID' => $navigation->parent_id, - 'navID' => $navigation->id, - 'active' => $navigation->is_active, - ), - ); + $navigationTree = []; + + $navigationMenu = new self(); + $navigationMenu->domain_id = $domainID; + $navigationMenu->orderBy('parent_id, weight'); + $navigationMenu->find(); + + while ($navigationMenu->fetch()) { + $navigationTree[$navigationMenu->id] = [ + 'attributes' => [ + 'label' => $navigationMenu->label, + 'name' => $navigationMenu->name, + 'url' => $navigationMenu->url, + 'icon' => $navigationMenu->icon, + 'weight' => $navigationMenu->weight, + 'permission' => $navigationMenu->permission, + 'operator' => $navigationMenu->permission_operator, + 'separator' => $navigationMenu->has_separator, + 'parentID' => $navigationMenu->parent_id, + 'navID' => $navigationMenu->id, + 'active' => $navigationMenu->is_active, + ], + ]; } return self::buildTree($navigationTree); @@ -307,7 +284,7 @@ public static function buildNavigationTree() { * @return array */ private static function buildTree($elements, $parentId = NULL) { - $branch = array(); + $branch = []; foreach ($elements as $id => $element) { if ($element['attributes']['parentID'] == $parentId) { @@ -323,41 +300,29 @@ private static function buildTree($elements, $parentId = NULL) { } /** - * Build menu. - * - * @return string + * buildNavigationTree retreives items in order. We call this function to + * ensure that any items added by the hook are also in the correct order. */ - public static function buildNavigation() { - $navigations = self::buildNavigationTree(); - $navigationString = ''; + public static function orderByWeight(&$navigations) { + // sort each item in navigations by weight + usort($navigations, function($a, $b) { - // run the Navigation through a hook so users can modify it - CRM_Utils_Hook::navigationMenu($navigations); - self::fixNavigationMenu($navigations); + // If no weight have been defined for an item put it at the end of the list + if (!isset($a['attributes']['weight'])) { + $a['attributes']['weight'] = 1000; + } + if (!isset($b['attributes']['weight'])) { + $b['attributes']['weight'] = 1000; + } + return $a['attributes']['weight'] - $b['attributes']['weight']; + }); - //skip children menu item if user don't have access to parent menu item - $skipMenuItems = array(); - foreach ($navigations as $key => $value) { - // Home is a special case - if ($value['attributes']['name'] != 'Home') { - $name = self::getMenuName($value, $skipMenuItems); - if ($name) { - //separator before - if (isset($value['attributes']['separator']) && $value['attributes']['separator'] == 2) { - $navigationString .= ''; - } - $removeCharacters = array('/', '!', '&', '*', ' ', '(', ')', '.'); - $navigationString .= '
    "; - if ($config->wkhtmltopdfPath) { + if (CRM_Core_Config::singleton()->wkhtmltopdfPath) { return self::_html2pdf_wkhtmltopdf($paper_size, $orientation, $margins, $html, $output, $fileName); } else { return self::_html2pdf_dompdf($paper_size, $orientation, $html, $output, $fileName); - //return self::_html2pdf_tcpdf($paper_size, $orientation, $margins, $html, $output, $fileName, $stationery_path); } } @@ -152,9 +142,10 @@ public static function _html2pdf_tcpdf($paper_size, $orientation, $margins, $htm // This function also uses the FPDI library documented at: http://www.setasign.com/products/fpdi/about/ // Syntax borrowed from https://github.com/jake-mw/CDNTaxReceipts/blob/master/cdntaxreceipts.functions.inc require_once 'tcpdf/tcpdf.php'; - require_once 'FPDI/fpdi.php'; // This library is only in the 'packages' area as of version 4.5 + // This library is only in the 'packages' area as of version 4.5 + require_once 'FPDI/fpdi.php'; - $paper_size_arr = array($paper_size[2], $paper_size[3]); + $paper_size_arr = [$paper_size[2], $paper_size[3]]; $pdf = new TCPDF($orientation, 'pt', $paper_size_arr); $pdf->Open(); @@ -189,7 +180,7 @@ public static function _html2pdf_tcpdf($paper_size, $orientation, $margins, $htm $pdf->Close(); $pdf_file = 'CiviLetter' . '.pdf'; $pdf->Output($pdf_file, 'D'); - CRM_Utils_System::civiExit(1); + CRM_Utils_System::civiExit(); } /** diff --git a/CRM/Utils/Pager.php b/CRM/Utils/Pager.php index bd33490a8361..390764d766ac 100644 --- a/CRM/Utils/Pager.php +++ b/CRM/Utils/Pager.php @@ -1,9 +1,9 @@ $start, 2 => $end, 3 => $params['total'])); + $statusMessage = ts('%1 - %2 of %3', [1 => $start, 2 => $end, 3 => $params['total']]); } $params['status'] = str_replace('%%StatusMessage%%', $statusMessage, $params['status']); - $this->_response = array( + $this->_response = [ 'first' => $this->getFirstPageLink(), 'back' => $this->getBackPageLink(), 'next' => $this->getNextPageLink(), @@ -104,21 +104,21 @@ public function __construct($params) { 'buttonTop' => CRM_Utils_Array::value('buttonTop', $params), 'buttonBottom' => CRM_Utils_Array::value('buttonBottom', $params), 'currentLocation' => $this->getCurrentLocation(), - ); + ]; /** * A page cannot have two variables with the same form name. Hence in the * pager display, we have a form submission at the top with the normal * page variable, but a different form element for one at the bottom. */ - $this->_response['titleTop'] = ts('Page %1 of %2', array( - 1 => '', - 2 => $this->_response['numPages'], - )); - $this->_response['titleBottom'] = ts('Page %1 of %2', array( - 1 => '', - 2 => $this->_response['numPages'], - )); + $this->_response['titleTop'] = ts('Page %1 of %2', [ + 1 => '', + 2 => $this->_response['numPages'], + ]); + $this->_response['titleBottom'] = ts('Page %1 of %2', [ + 1 => '', + 2 => $this->_response['numPages'], + ]); } /** @@ -148,8 +148,8 @@ public function initialize(&$params) { $params['separator'] = ''; $params['spacesBeforeSeparator'] = 1; $params['spacesAfterSeparator'] = 1; - $params['extraVars'] = array('force' => 1); - $params['excludeVars'] = array('reset', 'snippet', 'section'); + $params['extraVars'] = ['force' => 1]; + $params['excludeVars'] = ['reset', 'snippet', 'section']; // set previous and next text labels $params['prevImg'] = ' ' . ts('< Previous'); @@ -248,7 +248,7 @@ public function getOffsetAndRowCount() { $offset = ($pageId - 1) * $this->_perPage; - return array($offset, $this->_perPage); + return [$offset, $this->_perPage]; } /** diff --git a/CRM/Utils/PagerAToZ.php b/CRM/Utils/PagerAToZ.php index 8e0fccf207e0..32da0ee8cc46 100644 --- a/CRM/Utils/PagerAToZ.php +++ b/CRM/Utils/PagerAToZ.php @@ -1,9 +1,9 @@ fetch()) { - $dynamicAlphabets[] = $result->sort_name; + $dynamicAlphabets[] = strtoupper($result->sort_name); } return $dynamicAlphabets; } @@ -151,16 +151,18 @@ public static function createLinks(&$query, $sortByCharacter, $isDAO) { $qfKey = CRM_Utils_Array::value('qfKey', $query->_formValues); } if (empty($qfKey)) { - $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this, FALSE, NULL, $_REQUEST); + // CRM-20943 Can only pass variables by reference and also cannot use $this so using $empty setting to NULL which is default. + $emptyVariable = NULL; + $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $emptyVariable, FALSE, NULL, $_REQUEST); } - $aToZBar = array(); + $aToZBar = []; foreach ($AToZBar as $key => $link) { if ($link === NULL) { continue; } - $element = array(); + $element = []; if (in_array($link, $dynamicAlphabets)) { $klass = ''; if ($link == $sortByCharacter) { @@ -191,7 +193,7 @@ public static function createLinks(&$query, $sortByCharacter, $isDAO) { ), ts('All') ); - $aToZBar[] = array('item' => $url); + $aToZBar[] = ['item' => $url]; return $aToZBar; } diff --git a/CRM/Utils/PseudoConstant.php b/CRM/Utils/PseudoConstant.php index 6ffc725adfcc..7a3cf0d54768 100644 --- a/CRM/Utils/PseudoConstant.php +++ b/CRM/Utils/PseudoConstant.php @@ -1,9 +1,9 @@ hasDeclaredIndex($tableName, $columns) ) { $formattedQuery = $this->format($queryText, CRM_Utils_QueryFormatter::LANG_SQL_FTSBOOL); - $prefixedFieldNames = array(); + $prefixedFieldNames = []; foreach ($columns as $fieldName) { $prefixedFieldNames[] = "$tableAlias.$fieldName"; } @@ -287,8 +292,8 @@ protected function _formatFts($text, $mode) { */ protected function _formatFtsBool($text, $mode) { $result = NULL; - $operators = array('+', '-', '~', '(', ')'); - $wildCards = array('@', '%', '*'); + $operators = ['+', '-', '~', '(', ')']; + $wildCards = ['@', '%', '*']; $expression = preg_quote(implode('', array_merge($operators, $wildCards)), '/'); //Return if searched string ends with an unsupported operator. @@ -399,7 +404,7 @@ protected function _formatLike($text, $mode) { * @return string */ protected function mapWords($text, $template, $quotes = FALSE) { - $result = array(); + $result = []; foreach ($this->parseWords($text, $quotes) as $word) { $result[] = str_replace('word', $word, $template); } @@ -407,8 +412,8 @@ protected function mapWords($text, $template, $quotes = FALSE) { } /** - * @param $text - * @bool $quotes + * @param string $text + * @param bool $quotes * @return array */ protected function parseWords($text, $quotes) { @@ -458,13 +463,13 @@ protected function dedupeWildcards($text, $wildcard) { * @return array */ public static function getModes() { - return array( + return [ self::MODE_NONE, self::MODE_PHRASE, self::MODE_WILDPHRASE, self::MODE_WILDWORDS, self::MODE_WILDWORDS_SUFFIX, - ); + ]; } /** @@ -473,12 +478,12 @@ public static function getModes() { * @return array */ public static function getLanguages() { - return array( + return [ self::LANG_SOLR, self::LANG_SQL_FTS, self::LANG_SQL_FTSBOOL, self::LANG_SQL_LIKE, - ); + ]; } /** diff --git a/CRM/Utils/REST.php b/CRM/Utils/REST.php index bd88f4202714..b1df33aee17c 100644 --- a/CRM/Utils/REST.php +++ b/CRM/Utils/REST.php @@ -1,9 +1,9 @@ get('key'); // $session->set( 'key', $var ); - return self::simple(array('message' => "PONG: $key")); + return self::simple(['message' => "PONG: $key"]); } /** @@ -78,10 +80,10 @@ public static function ping($var = NULL) { * @return array */ public static function error($message = 'Unknown Error') { - $values = array( + $values = [ 'error_message' => $message, 'is_error' => 1, - ); + ]; return $values; } @@ -92,7 +94,7 @@ public static function error($message = 'Unknown Error') { * @return array */ public static function simple($params) { - $values = array('is_error' => 0); + $values = ['is_error' => 0]; $values += $params; return $values; } @@ -129,7 +131,7 @@ public static function output(&$result) { if (!$result) { $result = 0; } - $result = self::simple(array('result' => $result)); + $result = self::simple(['result' => $result]); } elseif (is_array($result)) { if (CRM_Utils_Array::isHierarchical($result)) { @@ -146,6 +148,7 @@ public static function output(&$result) { if (!empty($requestParams['json'])) { if (!empty($requestParams['prettyprint'])) { // Don't set content-type header for api explorer output + return json_encode(array_merge($result), JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE); return self::jsonFormated(array_merge($result)); } CRM_Utils_System::setHttpHeader('Content-Type', 'application/json'); @@ -181,100 +184,6 @@ public static function output(&$result) { return $xml; } - /** - * @param $data - * - * @deprecated - switch to native JSON_PRETTY_PRINT when we drop support for php 5.3 - * - * @return string - */ - public static function jsonFormated($data) { - // If php is 5.4+ we can use the native method - if (defined('JSON_PRETTY_PRINT')) { - return json_encode($data, JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE); - } - - // PHP 5.3 shim - $json = str_replace('\/', '/', json_encode($data)); - $tabcount = 0; - $result = ''; - $inquote = FALSE; - $inarray = FALSE; - $ignorenext = FALSE; - - $tab = "\t"; - $newline = "\n"; - - for ($i = 0; $i < strlen($json); $i++) { - $char = $json[$i]; - - if ($ignorenext) { - $result .= $char; - $ignorenext = FALSE; - } - else { - switch ($char) { - case '{': - if ($inquote) { - $result .= $char; - } - else { - $inarray = FALSE; - $tabcount++; - $result .= $char . $newline . str_repeat($tab, $tabcount); - } - break; - - case '}': - if ($inquote) { - $result .= $char; - } - else { - $tabcount--; - $result = trim($result) . $newline . str_repeat($tab, $tabcount) . $char; - } - break; - - case ',': - if ($inquote || $inarray) { - $result .= $char; - } - else { - $result .= $char . $newline . str_repeat($tab, $tabcount); - } - break; - - case '"': - $inquote = !$inquote; - $result .= $char; - break; - - case '\\': - if ($inquote) { - $ignorenext = TRUE; - } - $result .= $char; - break; - - case '[': - $inarray = TRUE; - $result .= $char; - break; - - case ']': - $inarray = FALSE; - $result .= $char; - break; - - default: - $result .= $char; - } - } - } - - return $result; - } - /** * @return array|int */ @@ -309,7 +218,7 @@ public static function handle() { } else { // or the api format (entity+action) - $args = array(); + $args = []; $args[0] = 'civicrm'; $args[1] = CRM_Utils_Array::value('entity', $requestParams); $args[2] = CRM_Utils_Array::value('action', $requestParams); @@ -370,7 +279,7 @@ public static function process(&$args, $params) { return self::error('Unknown function invocation.'); } - return call_user_func(array($params['className'], $params['fnName']), $params); + return call_user_func([$params['className'], $params['fnName']], $params); } if (!array_key_exists('version', $params)) { @@ -385,22 +294,25 @@ public static function process(&$args, $params) { } if ($_SERVER['REQUEST_METHOD'] == 'GET' && - strtolower(substr($args[2], 0, 3)) != 'get' && - strtolower($args[2] != 'check')) { + strtolower(substr($args[2], 0, 3)) != 'get' && + strtolower($args[2] != 'check')) { // get only valid for non destructive methods require_once 'api/v3/utils.php'; return civicrm_api3_create_error("SECURITY: All requests that modify the database must be http POST, not GET.", - array( + [ 'IP' => $_SERVER['REMOTE_ADDR'], 'level' => 'security', 'referer' => $_SERVER['HTTP_REFERER'], 'reason' => 'Destructive HTTP GET', - ) + ] ); } // trap all fatal errors - $errorScope = CRM_Core_TemporaryErrorScope::create(array('CRM_Utils_REST', 'fatal')); + $errorScope = CRM_Core_TemporaryErrorScope::create([ + 'CRM_Utils_REST', + 'fatal', + ]); $result = civicrm_api($args[1], $args[2], $params); unset($errorScope); @@ -415,21 +327,25 @@ public static function process(&$args, $params) { */ public static function &buildParamList() { $requestParams = CRM_Utils_Request::exportValues(); - $params = array(); + $params = []; - $skipVars = array( + $skipVars = [ 'q' => 1, 'json' => 1, 'key' => 1, 'api_key' => 1, 'entity' => 1, 'action' => 1, - ); + ]; if (array_key_exists('json', $requestParams) && $requestParams['json'][0] == "{") { $params = json_decode($requestParams['json'], TRUE); if ($params === NULL) { - CRM_Utils_JSON::output(array('is_error' => 1, 'error_message', 'Unable to decode supplied JSON.')); + CRM_Utils_JSON::output([ + 'is_error' => 1, + 0 => 'error_message', + 1 => 'Unable to decode supplied JSON.', + ]); } } foreach ($requestParams as $n => $v) { @@ -450,7 +366,7 @@ public static function &buildParamList() { */ public static function fatal($pearError) { CRM_Utils_System::setHttpHeader('Content-Type', 'text/xml'); - $error = array(); + $error = []; $error['code'] = $pearError->getCode(); $error['error_message'] = $pearError->getMessage(); $error['mode'] = $pearError->getMode(); @@ -471,7 +387,7 @@ public static function fatal($pearError) { public static function loadTemplate() { $request = CRM_Utils_Request::retrieve('q', 'String'); if (FALSE !== strpos($request, '..')) { - die ("SECURITY FATAL: the url can't contain '..'. Please report the issue on the forum at civicrm.org"); + die("SECURITY FATAL: the url can't contain '..'. Please report the issue on the forum at civicrm.org"); } $request = explode('/', $request); @@ -483,15 +399,17 @@ public static function loadTemplate() { CRM_Utils_System::setTitle("$entity::$tplfile inline $tpl"); if (!$smarty->template_exists($tpl)) { CRM_Utils_System::setHttpHeader("Status", "404 Not Found"); - die ("Can't find the requested template file templates/$tpl"); + die("Can't find the requested template file templates/$tpl"); } - if (array_key_exists('id', $_GET)) {// special treatmenent, because it's often used - $smarty->assign('id', (int) $_GET['id']);// an id is always positive + // special treatmenent, because it's often used + if (array_key_exists('id', $_GET)) { + // an id is always positive + $smarty->assign('id', (int) $_GET['id']); } $pos = strpos(implode(array_keys($_GET)), '<'); if ($pos !== FALSE) { - die ("SECURITY FATAL: one of the param names contains <"); + die("SECURITY FATAL: one of the param names contains <"); } $param = array_map('htmlentities', $_GET); unset($param['q']); @@ -537,12 +455,12 @@ public static function ajaxJson() { ) ) { $error = civicrm_api3_create_error("SECURITY ALERT: Ajax requests can only be issued by javascript clients, eg. CRM.api3().", - array( + [ 'IP' => $_SERVER['REMOTE_ADDR'], 'level' => 'security', 'referer' => $_SERVER['HTTP_REFERER'], 'reason' => 'CSRF suspected', - ) + ] ); CRM_Utils_JSON::output($error); } @@ -558,21 +476,25 @@ public static function ajaxJson() { $entity = CRM_Utils_String::munge(CRM_Utils_Array::value('entity', $requestParams)); $action = CRM_Utils_String::munge(CRM_Utils_Array::value('action', $requestParams)); if (!is_array($params)) { - CRM_Utils_JSON::output(array( - 'is_error' => 1, - 'error_message' => 'invalid json format: ?{"param_with_double_quote":"value"}', - )); + CRM_Utils_JSON::output([ + 'is_error' => 1, + 'error_message' => 'invalid json format: ?{"param_with_double_quote":"value"}', + ]); } $params['check_permissions'] = TRUE; $params['version'] = 3; - $_GET['json'] = $requestParams['json'] = 1; // $requestParams is local-only; this line seems pointless unless there's a side-effect influencing other functions + // $requestParams is local-only; this line seems pointless unless there's a side-effect influencing other functions + $_GET['json'] = $requestParams['json'] = 1; if (!$params['sequential']) { $params['sequential'] = 1; } // trap all fatal errors - $errorScope = CRM_Core_TemporaryErrorScope::create(array('CRM_Utils_REST', 'fatal')); + $errorScope = CRM_Core_TemporaryErrorScope::create([ + 'CRM_Utils_REST', + 'fatal', + ]); $result = civicrm_api($entity, $action, $params); unset($errorScope); @@ -600,12 +522,12 @@ public static function ajax() { ) { require_once 'api/v3/utils.php'; $error = civicrm_api3_create_error("SECURITY ALERT: Ajax requests can only be issued by javascript clients, eg. CRM.api3().", - array( + [ 'IP' => $_SERVER['REMOTE_ADDR'], 'level' => 'security', 'referer' => $_SERVER['HTTP_REFERER'], 'reason' => 'CSRF suspected', - ) + ] ); CRM_Utils_JSON::output($error); } @@ -615,11 +537,14 @@ public static function ajax() { $entity = CRM_Utils_Array::value('entity', $requestParams); $action = CRM_Utils_Array::value('action', $requestParams); if (!$entity || !$action) { - $err = array('error_message' => 'missing mandatory params "entity=" or "action="', 'is_error' => 1); + $err = [ + 'error_message' => 'missing mandatory params "entity=" or "action="', + 'is_error' => 1, + ]; echo self::output($err); CRM_Utils_System::civiExit(); } - $args = array('civicrm', $entity, $action); + $args = ['civicrm', $entity, $action]; } else { $args = explode('/', $q); @@ -651,14 +576,14 @@ public static function ajax() { * @return array */ public static function processMultiple() { - $output = array(); + $output = []; foreach (json_decode($_REQUEST['json'], TRUE) as $key => $call) { - $args = array( + $args = [ 'civicrm', $call[0], $call[1], - ); - $output[$key] = self::process($args, CRM_Utils_Array::value(2, $call, array())); + ]; + $output[$key] = self::process($args, CRM_Utils_Array::value(2, $call, [])); } return $output; } @@ -676,8 +601,9 @@ public function loadCMSBootstrap() { // Proceed with bootstrap for "?q=civicrm/X/Y" but not "?q=civicrm/ping" if (!empty($q)) { if (count($args) == 2 && $args[1] == 'ping') { - CRM_Utils_System::loadBootStrap(array(), FALSE, FALSE); - return NULL; // this is pretty wonky but maybe there's some reason I can't see + CRM_Utils_System::loadBootStrap([], FALSE, FALSE); + // this is pretty wonky but maybe there's some reason I can't see + return NULL; } if (count($args) != 3) { return self::error('ERROR: Malformed REST path'); @@ -692,7 +618,7 @@ public function loadCMSBootstrap() { // FIXME: At time of writing, this doesn't actually do anything because // authenticateKey abends, but that's a bad behavior which sends a // malformed response. - CRM_Utils_System::loadBootStrap(array(), FALSE, FALSE); + CRM_Utils_System::loadBootStrap([], FALSE, FALSE); return self::error('Failed to authenticate key'); } @@ -701,7 +627,7 @@ public function loadCMSBootstrap() { $store = NULL; $api_key = CRM_Utils_Request::retrieve('api_key', 'String', $store, FALSE, NULL, 'REQUEST'); if (empty($api_key)) { - CRM_Utils_System::loadBootStrap(array(), FALSE, FALSE); + CRM_Utils_System::loadBootStrap([], FALSE, FALSE); return self::error("FATAL: mandatory param 'api_key' (user key) missing"); } $contact_id = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $api_key, 'id', 'api_key'); @@ -711,17 +637,17 @@ public function loadCMSBootstrap() { } if ($uid && $contact_id) { - CRM_Utils_System::loadBootStrap(array('uid' => $uid), TRUE, FALSE); + CRM_Utils_System::loadBootStrap(['uid' => $uid], TRUE, FALSE); $session = CRM_Core_Session::singleton(); $session->set('ufID', $uid); $session->set('userID', $contact_id); CRM_Core_DAO::executeQuery('SET @civicrm_user_id = %1', - array(1 => array($contact_id, 'Integer')) + [1 => [$contact_id, 'Integer']] ); return NULL; } else { - CRM_Utils_System::loadBootStrap(array(), FALSE, FALSE); + CRM_Utils_System::loadBootStrap([], FALSE, FALSE); return self::error('ERROR: No CMS user associated with given api-key'); } } diff --git a/CRM/Utils/ReCAPTCHA.php b/CRM/Utils/ReCAPTCHA.php index aa27d26dd6ba..fab82510ce85 100644 --- a/CRM/Utils/ReCAPTCHA.php +++ b/CRM/Utils/ReCAPTCHA.php @@ -1,9 +1,9 @@ recaptchaPublicKey == NULL || $config->recaptchaPublicKey == "") { + return FALSE; + } + return TRUE; + } + + /** + * Check if reCaptcha has to be added on form forcefully. + */ + public static function hasToAddForcefully() { + $config = CRM_Core_Config::singleton(); + if (!$config->forceRecaptcha) { + return FALSE; + } + return TRUE; + } + /** * Add element to form. * @@ -75,11 +97,10 @@ public static function add(&$form) { require_once 'packages/recaptcha/recaptchalib.php'; } - // See if we are using SSL - if (CRM_Utils_System::isSSL()) { - $useSSL = TRUE; - } - $html = recaptcha_get_html($config->recaptchaPublicKey, $error, $useSSL); + // Load the Recaptcha api.js over HTTPS + $useHTTPS = TRUE; + + $html = recaptcha_get_html($config->recaptchaPublicKey, $error, $useHTTPS); $form->assign('recaptchaHTML', $html); $form->assign('recaptchaOptions', $config->recaptchaOptions); @@ -99,4 +120,17 @@ public static function add(&$form) { } } + /** + * Enable ReCAPTCHA on Contribution form + * + * @param CRM_Core_Form $form + */ + public static function enableCaptchaOnForm(&$form) { + $captcha = CRM_Utils_ReCAPTCHA::singleton(); + if ($captcha->hasSettingsAvailable()) { + $captcha->add($form); + $form->assign('isCaptcha', TRUE); + } + } + } diff --git a/CRM/Utils/Recent.php b/CRM/Utils/Recent.php index 7f8e5362727d..55ccf47b4b5c 100644 --- a/CRM/Utils/Recent.php +++ b/CRM/Utils/Recent.php @@ -1,9 +1,9 @@ get(self::STORE_NAME); if (!self::$_recent) { - self::$_recent = array(); + self::$_recent = []; } } } @@ -104,7 +104,7 @@ public static function add( $type, $contactId, $contactName, - $others = array() + $others = [] ) { self::initialize(); @@ -116,7 +116,7 @@ public static function add( // make sure item is not already present in list for ($i = 0; $i < count(self::$_recent); $i++) { - if (self::$_recent[$i]['url'] == $url) { + if (self::$_recent[$i]['type'] === $type && self::$_recent[$i]['id'] == $id) { // delete item from array array_splice(self::$_recent, $i, 1); break; @@ -124,11 +124,11 @@ public static function add( } if (!is_array($others)) { - $others = array(); + $others = []; } array_unshift(self::$_recent, - array( + [ 'title' => $title, 'url' => $url, 'id' => $id, @@ -140,7 +140,7 @@ public static function add( 'image_url' => CRM_Utils_Array::value('imageUrl', $others), 'edit_url' => CRM_Utils_Array::value('editUrl', $others), 'delete_url' => CRM_Utils_Array::value('deleteUrl', $others), - ) + ] ); if (count(self::$_recent) > self::$_maxItems) { @@ -162,7 +162,7 @@ public static function del($recentItem) { self::initialize(); $tempRecent = self::$_recent; - self::$_recent = ''; + self::$_recent = []; // make sure item is not already present in list for ($i = 0; $i < count($tempRecent); $i++) { @@ -174,6 +174,7 @@ public static function del($recentItem) { } } + CRM_Utils_Hook::recent(self::$_recent); $session = CRM_Core_Session::singleton(); $session->set(self::STORE_NAME, self::$_recent); } @@ -189,7 +190,7 @@ public static function delContact($id) { $tempRecent = self::$_recent; - self::$_recent = ''; + self::$_recent = []; // rebuild recent. for ($i = 0; $i < count($tempRecent); $i++) { @@ -200,15 +201,17 @@ public static function delContact($id) { self::$_recent[] = $tempRecent[$i]; } + CRM_Utils_Hook::recent(self::$_recent); $session = CRM_Core_Session::singleton(); $session->set(self::STORE_NAME, self::$_recent); } /** * Check if a provider is allowed to add stuff. - * If correspondig setting is empty, all are allowed + * If corresponding setting is empty, all are allowed * * @param string $providerName + * @return bool */ public static function isProviderEnabled($providerName) { @@ -230,9 +233,11 @@ public static function isProviderEnabled($providerName) { /** * Gets the list of available providers to civi's recent items stack + * + * @return array */ public static function getProviders() { - $providers = array( + $providers = [ 'Contact' => ts('Contacts'), 'Relationship' => ts('Relationships'), 'Activity' => ts('Activities'), @@ -246,7 +251,7 @@ public static function getProviders() { 'Pledge' => ts('Pledges'), 'Event' => ts('Events'), 'Campaign' => ts('Campaigns'), - ); + ]; return $providers; } diff --git a/CRM/Utils/Request.php b/CRM/Utils/Request.php index 2748e07d7df8..95427c78a719 100644 --- a/CRM/Utils/Request.php +++ b/CRM/Utils/Request.php @@ -1,9 +1,9 @@ $name))); + if ($isThrowException) { + throw new CRM_Core_Exception(ts("Could not find valid value for %1", [1 => $name])); + } + CRM_Core_Error::fatal(ts("Could not find valid value for %1", [1 => $name])); } if (!isset($value) && $default) { @@ -125,8 +126,10 @@ public static function retrieve($name, $type, &$store = NULL, $abort = FALSE, $d } // minor hack for action - if ($name == 'action' && is_string($value)) { - $value = CRM_Core_Action::resolve($value); + if ($name == 'action') { + if (!is_numeric($value) && is_string($value)) { + $value = CRM_Core_Action::resolve($value); + } } if (isset($value) && $store) { @@ -143,9 +146,9 @@ public static function retrieve($name, $type, &$store = NULL, $abort = FALSE, $d * @param array $method - '$_GET', '$_POST' or '$_REQUEST'. * * @return mixed - * The value of the variable + * The value of the variable */ - public static function getValue($name, $method) { + protected static function getValue($name, $method) { if (isset($method[$name])) { return $method[$name]; } @@ -165,6 +168,10 @@ public static function getValue($name, $method) { } /** + * @deprecated + * + * We should use a function that checks url values. + * * This is a replacement for $_REQUEST which includes $_GET/$_POST * but excludes $_COOKIE / $_ENV / $_SERVER. * @@ -176,7 +183,7 @@ public static function exportValues() { // http://www.php.net/manual/en/ini.core.php#ini.request-order // http://www.php.net/manual/en/ini.core.php#ini.variables-order - $result = array(); + $result = []; if ($_GET) { $result = array_merge($result, $_GET); } @@ -186,4 +193,64 @@ public static function exportValues() { return $result; } + /** + * Retrieve a variable from the http request. + * + * @param string $name + * Name of the variable to be retrieved. + * @param string $type + * Type of the variable (see CRM_Utils_Type for details). + * Most common options are: + * - 'Integer' + * - 'Positive' + * - 'CommaSeparatedIntegers' + * - 'Boolean' + * - 'String' + * + * @param mixed $defaultValue + * Default value of the variable if not present. + * @param bool $isRequired + * Is the variable required for this function to proceed without an exception. + * @param string $method + * Where to look for the value - GET|POST|REQUEST + * + * @return mixed + * @throws \CRM_Core_Exception + */ + public static function retrieveValue($name, $type, $defaultValue = NULL, $isRequired = FALSE, $method = 'REQUEST') { + $null = NULL; + return CRM_Utils_Request::retrieve((string) $name, (string) $type, $null, (bool) $isRequired, $defaultValue, $method, TRUE); + } + + /** + * Retrieve the component from the action attribute of a form. + * + * Contribution Page forms and Event Management forms detect the value of a + * component (and therefore the desired tab key) by reaching into the "action" + * attribute of a form and reading the final item of the path. In WordPress, + * however, the URL may be urlencoded, and so the URL may need to be decoded + * before parsing it. + * + * @see https://lab.civicrm.org/dev/wordpress/issues/12#note_10699 + * + * @param array $attributes + * The form attributes array. + * + * @return string + * The desired value. + */ + public static function retrieveComponent($attributes) { + $url = CRM_Utils_Array::value('action', $attributes); + // Whilst the following is a fallible universal test for urlencoded URLs, + // thankfully the "action" URL has a limited and predictable form and + // therefore this comparison is sufficient for our purposes. + if (rawurlencode(rawurldecode($url)) !== $url) { + $value = strtolower(basename(rawurldecode($url))); + } + else { + $value = strtolower(basename($url)); + } + return $value; + } + } diff --git a/CRM/Utils/Rule.php b/CRM/Utils/Rule.php index a40286d4b5c1..940128d7156c 100644 --- a/CRM/Utils/Rule.php +++ b/CRM/Utils/Rule.php @@ -1,9 +1,9 @@ + * * @param $value + * @return bool + */ + public static function color($value) { + return (bool) preg_match('/^#([\da-fA-F]{6})$/', $value); + } + + /** + * Strip thousand separator from a money string. + * + * Note that this should be done at the form layer. Once we are processing + * money at the BAO or processor layer we should be working with something that + * is already in a normalised format. + * + * @param string $value * - * @return mixed + * @return string */ public static function cleanMoney($value) { // first remove all white space - $value = str_replace(array(' ', "\t", "\n"), '', $value); + $value = str_replace([' ', "\t", "\n"], '', $value); $config = CRM_Core_Config::singleton(); //CRM-14868 $currencySymbols = CRM_Core_PseudoConstant::get( 'CRM_Contribute_DAO_Contribution', - 'currency', array( + 'currency', [ 'keyColumn' => 'name', 'labelColumn' => 'symbol', - ) + ] ); $value = str_replace($currencySymbols, '', $value); @@ -574,7 +631,10 @@ public static function money($value) { return TRUE; } - return preg_match('/(^-?\d+\.\d?\d?$)|(^-?\.\d\d?$)/', $value) ? TRUE : FALSE; + // Allow values such as -0, 1.024555, -.1 + // We need to support multiple decimal places here, not just the number allowed by locale + // otherwise tax calculations break when you want the inclusive amount to be a round number (eg. £10 inc. VAT requires 8.333333333 here). + return preg_match('/(^-?\d+\.?\d*$)|(^-?\.\d+$)/', $value) ? TRUE : FALSE; } /** @@ -790,6 +850,25 @@ public static function xssString($value) { } } + /** + * Validate json string for xss + * + * @param string $value + * + * @return bool + * False if invalid, true if valid / safe. + */ + public static function json($value) { + if (!self::xssString($value)) { + return FALSE; + } + $array = json_decode($value, TRUE); + if (!$array || !is_array($array)) { + return FALSE; + } + return self::arrayValue($array); + } + /** * @param $path * @@ -904,8 +983,39 @@ public static function validDateRange($fields, $fieldName, &$errors, $title) { $highDate = strtotime($fields[$fieldName . '_high']); if ($lowDate > $highDate) { - $errors[$fieldName . '_range_error'] = ts('%1: Please check that your date range is in correct chronological order.', array(1 => $title)); + $errors[$fieldName . '_range_error'] = ts('%1: Please check that your date range is in correct chronological order.', [1 => $title]); + } + } + + /** + * @param string $key Extension Key to check + * @return bool + */ + public static function checkExtensionKeyIsValid($key = NULL) { + if (!empty($key) && !preg_match('/^[0-9a-zA-Z._-]+$/', $key)) { + return FALSE; + } + return TRUE; + } + + /** + * Validate array recursively checking keys and values. + * + * @param array $array + * @return bool + */ + protected static function arrayValue($array) { + foreach ($array as $key => $item) { + if (is_array($item)) { + if (!self::xssString($key) || !self::arrayValue($item)) { + return FALSE; + } + } + if (!self::xssString($key) || !self::xssString($item)) { + return FALSE; + } } + return TRUE; } } diff --git a/CRM/Utils/SQL.php b/CRM/Utils/SQL.php index 574b3a6cfeca..d7aab2132bc5 100644 --- a/CRM/Utils/SQL.php +++ b/CRM/Utils/SQL.php @@ -1,9 +1,9 @@ addSelectWhereClause() as $field => $vals) { if ($vals && $field == $joinColumn) { $clauses = array_merge($clauses, (array) $vals); @@ -59,4 +59,102 @@ public static function mergeSubquery($entity, $joinColumn = 'id') { return $clauses; } + /** + * Get current sqlModes of the session + * @return array + */ + public static function getSqlModes() { + $sqlModes = explode(',', CRM_Core_DAO::singleValueQuery('SELECT @@sql_mode')); + return $sqlModes; + } + + /** + * Checks if this system enforce the MYSQL mode ONLY_FULL_GROUP_BY. + * This function should be named supportsAnyValueAndEnforcesFullGroupBY(), + * but should be deprecated instead. + * + * @return mixed + * @deprecated + */ + public static function supportsFullGroupBy() { + // CRM-21455 MariaDB 10.2 does not support ANY_VALUE + $version = self::getDatabaseVersion(); + + if (stripos($version, 'mariadb') !== FALSE) { + return FALSE; + } + + return version_compare($version, '5.7', '>='); + } + + /** + * Disable ONLY_FULL_GROUP_BY for MySQL versions lower then 5.7 + * + * @return bool + */ + public static function disableFullGroupByMode() { + $sqlModes = self::getSqlModes(); + + // Disable only_full_group_by mode for lower sql versions. + if (!self::supportsFullGroupBy() || (!empty($sqlModes) && !in_array('ONLY_FULL_GROUP_BY', $sqlModes))) { + if ($key = array_search('ONLY_FULL_GROUP_BY', $sqlModes)) { + unset($sqlModes[$key]); + CRM_Core_DAO::executeQuery("SET SESSION sql_mode = '" . implode(',', $sqlModes) . "'"); + } + return TRUE; + } + + return FALSE; + } + + /** + * CHeck if ONLY_FULL_GROUP_BY is in the global sql_modes + * @return bool + */ + public static function isGroupByModeInDefault() { + $sqlModes = explode(',', CRM_Core_DAO::singleValueQuery('SELECT @@global.sql_mode')); + if (!in_array('ONLY_FULL_GROUP_BY', $sqlModes)) { + return FALSE; + } + return TRUE; + } + + /** + * Does the DB version support mutliple locks per + * + * https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock + * + * As an interim measure we ALSO require CIVICRM_SUPPORT_MULTIPLE_LOCKS to be defined. + * + * This is a conservative measure to introduce the change which we expect to deprecate later. + * + * @todo we only check mariadb & mysql right now but maybe can add percona. + */ + public static function supportsMultipleLocks() { + if (!defined('CIVICRM_SUPPORT_MULTIPLE_LOCKS')) { + return FALSE; + } + static $isSupportLocks = NULL; + if (!isset($isSupportLocks)) { + $version = self::getDatabaseVersion(); + if (stripos($version, 'mariadb') !== FALSE) { + $isSupportLocks = version_compare($version, '10.0.2', '>='); + } + else { + $isSupportLocks = version_compare($version, '5.7.5', '>='); + } + } + + return $isSupportLocks; + } + + /** + * Get the version string for the database. + * + * @return string + */ + public static function getDatabaseVersion() { + return CRM_Core_DAO::singleValueQuery('SELECT VERSION()'); + } + } diff --git a/CRM/Utils/SQL/BaseParamQuery.php b/CRM/Utils/SQL/BaseParamQuery.php new file mode 100644 index 000000000000..159f56a5fad6 --- /dev/null +++ b/CRM/Utils/SQL/BaseParamQuery.php @@ -0,0 +1,241 @@ +strict = $strict; + return $this; + } + + /** + * Given a string like "field_name = @value", replace "@value" with an escaped SQL string + * + * @param string $expr SQL expression + * @param null|array $args a list of values to insert into the SQL expression; keys are prefix-coded: + * prefix '@' => escape SQL + * prefix '#' => literal number, skip escaping but do validation + * prefix '!' => literal, skip escaping and validation + * if a value is an array, then it will be imploded + * + * PHP NULL's will be treated as SQL NULL's. The PHP string "null" will be treated as a string. + * + * @param string $activeMode + * + * @return string + */ + public function interpolate($expr, $args, $activeMode = self::INTERPOLATE_INPUT) { + if ($args === NULL) { + return $expr; + } + else { + if ($this->mode === self::INTERPOLATE_AUTO) { + $this->mode = $activeMode; + } + elseif ($activeMode !== $this->mode) { + throw new RuntimeException("Cannot mix interpolation modes."); + } + + $select = $this; + return preg_replace_callback('/([#!@])([a-zA-Z0-9_]+)/', function($m) use ($select, $args) { + if (array_key_exists($m[2], $args)) { + $values = $args[$m[2]]; + } + elseif (array_key_exists($m[1] . $m[2], $args)) { + // Backward compat. Keys in $args look like "#myNumber" or "@myString". + $values = $args[$m[1] . $m[2]]; + } + elseif ($select->strict) { + throw new CRM_Core_Exception('Cannot build query. Variable "' . $m[1] . $m[2] . '" is unknown.'); + } + else { + // Unrecognized variables are ignored. Mitigate risk of accidents. + return $m[0]; + } + $values = is_array($values) ? $values : [$values]; + switch ($m[1]) { + case '@': + $parts = array_map([$select, 'escapeString'], $values); + return implode(', ', $parts); + + // TODO: ensure all uses of this un-escaped literal are safe + case '!': + return implode(', ', $values); + + case '#': + foreach ($values as $valueKey => $value) { + if ($value === NULL) { + $values[$valueKey] = 'NULL'; + } + elseif (!is_numeric($value)) { + //throw new API_Exception("Failed encoding non-numeric value" . var_export(array($m[0] => $values), TRUE)); + throw new CRM_Core_Exception("Failed encoding non-numeric value (" . $m[0] . ")"); + } + } + return implode(', ', $values); + + default: + throw new CRM_Core_Exception("Unrecognized prefix"); + } + }, $expr); + } + } + + /** + * @param string|NULL $value + * @return string + * SQL expression, e.g. "it\'s great" (with-quotes) or NULL (without-quotes) + */ + public function escapeString($value) { + return $value === NULL ? 'NULL' : '"' . CRM_Core_DAO::escapeString($value) . '"'; + } + + /** + * Set one (or multiple) parameters to interpolate into the query. + * + * @param array|string $keys + * Key name, or an array of key-value pairs. + * @param null|mixed $value + * The new value of the parameter. + * Values may be strings, ints, or arrays thereof -- provided that the + * SQL query uses appropriate prefix (e.g. "@", "!", "#"). + * @return $this + */ + public function param($keys, $value = NULL) { + if ($this->mode === self::INTERPOLATE_AUTO) { + $this->mode = self::INTERPOLATE_OUTPUT; + } + elseif ($this->mode !== self::INTERPOLATE_OUTPUT) { + throw new RuntimeException("Select::param() only makes sense when interpolating on output."); + } + + if (is_array($keys)) { + foreach ($keys as $k => $v) { + $this->params[$k] = $v; + } + } + else { + $this->params[$keys] = $value; + } + return $this; + } + + /** + * Has an offset been set. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) { + return isset($this->params[$offset]); + } + + /** + * Get the value of a SQL parameter. + * + * @code + * $select['cid'] = 123; + * $select->where('contact.id = #cid'); + * echo $select['cid']; + * @endCode + * + * @param string $offset + * @return mixed + * @see param() + * @see ArrayAccess::offsetGet + */ + public function offsetGet($offset) { + return $this->params[$offset]; + } + + /** + * Set the value of a SQL parameter. + * + * @code + * $select['cid'] = 123; + * $select->where('contact.id = #cid'); + * echo $select['cid']; + * @endCode + * + * @param string $offset + * @param mixed $value + * The new value of the parameter. + * Values may be strings, ints, or arrays thereof -- provided that the + * SQL query uses appropriate prefix (e.g. "@", "!", "#"). + * @see param() + * @see ArrayAccess::offsetSet + */ + public function offsetSet($offset, $value) { + $this->param($offset, $value); + } + + /** + * Unset the value of a SQL parameter. + * + * @param string $offset + * @see param() + * @see ArrayAccess::offsetUnset + */ + public function offsetUnset($offset) { + unset($this->params[$offset]); + } + +} diff --git a/CRM/Utils/SQL/Delete.php b/CRM/Utils/SQL/Delete.php new file mode 100644 index 000000000000..24d0f885fbb4 --- /dev/null +++ b/CRM/Utils/SQL/Delete.php @@ -0,0 +1,269 @@ +where('activity_type_id = #type', array('type' => 234)) + * ->where('status_id IN (#statuses)', array('statuses' => array(1,2,3)) + * ->where('subject like @subj', array('subj' => '%hello%')) + * ->where('!dynamicColumn = 1', array('dynamicColumn' => 'coalesce(is_active,0)')) + * ->where('!column = @value', array( + * 'column' => $customField->column_name, + * 'value' => $form['foo'] + * )) + * echo $del->toSQL(); + * @endcode + * + * Design principles: + * - Portable + * - No knowledge of the underlying SQL API (except for escaping -- CRM_Core_DAO::escapeString) + * - No knowledge of the underlying data model + * - SQL clauses correspond to PHP functions ($select->where("foo_id=123")) + * - Variable escaping is concise and controllable based on prefixes, eg + * - similar to Drupal's t() + * - use "@varname" to insert the escaped value + * - use "!varname" to insert raw (unescaped) values + * - use "#varname" to insert a numerical value (these are validated but not escaped) + * - to disable any preprocessing, simply omit the variable list + * - control characters (@!#) are mandatory in expressions but optional in arg-keys + * - Variables may be individual values or arrays; arrays are imploded with commas + * - Conditionals are AND'd; if you need OR's, do it yourself + * - Use classes/functions with documentation (rather than undocumented array-trees) + * - For any given string, interpolation is only performed once. After an interpolation, + * a string may never again be subjected to interpolation. + * + * The "interpolate-once" principle can be enforced by either interpolating on input + * xor output. The notations for input and output interpolation are a bit different, + * and they may not be mixed. + * + * @code + * // Interpolate on input. Set params when using them. + * $select->where('activity_type_id = #type', array( + * 'type' => 234, + * )); + * + * // Interpolate on output. Set params independently. + * $select + * ->where('activity_type_id = #type') + * ->param('type', 234), + * @endcode + * + * @package CRM + * @copyright CiviCRM LLC (c) 2004-2019 + */ +class CRM_Utils_SQL_Delete extends CRM_Utils_SQL_BaseParamQuery { + + private $from; + private $wheres = []; + + /** + * Create a new DELETE query. + * + * @param string $from + * Table-name and optional alias. + * @param array $options + * @return CRM_Utils_SQL_Delete + */ + public static function from($from, $options = []) { + return new self($from, $options); + } + + /** + * Create a new DELETE query. + * + * @param string $from + * Table-name and optional alias. + * @param array $options + */ + public function __construct($from, $options = []) { + $this->from = $from; + $this->mode = isset($options['mode']) ? $options['mode'] : self::INTERPOLATE_AUTO; + } + + /** + * Make a new copy of this query. + * + * @return CRM_Utils_SQL_Delete + */ + public function copy() { + return clone $this; + } + + /** + * Merge something or other. + * + * @param CRM_Utils_SQL_Delete $other + * @param array|NULL $parts + * ex: 'wheres' + * @return CRM_Utils_SQL_Delete + */ + public function merge($other, $parts = NULL) { + if ($other === NULL) { + return $this; + } + + if ($this->mode === self::INTERPOLATE_AUTO) { + $this->mode = $other->mode; + } + elseif ($other->mode === self::INTERPOLATE_AUTO) { + // Noop. + } + elseif ($this->mode !== $other->mode) { + // Mixing modes will lead to someone getting an expected substitution. + throw new RuntimeException("Cannot merge queries that use different interpolation modes ({$this->mode} vs {$other->mode})."); + } + + $arrayFields = ['wheres', 'params']; + foreach ($arrayFields as $f) { + if ($parts === NULL || in_array($f, $parts)) { + $this->{$f} = array_merge($this->{$f}, $other->{$f}); + } + } + + $flatFields = ['from']; + foreach ($flatFields as $f) { + if ($parts === NULL || in_array($f, $parts)) { + if ($other->{$f} !== NULL) { + $this->{$f} = $other->{$f}; + } + } + } + + return $this; + } + + /** + * Limit results by adding extra condition(s) to the WHERE clause + * + * @param string|array $exprs list of SQL expressions + * @param null|array $args use NULL to disable interpolation; use an array of variables to enable + * @return CRM_Utils_SQL_Delete + */ + public function where($exprs, $args = NULL) { + $exprs = (array) $exprs; + foreach ($exprs as $expr) { + $evaluatedExpr = $this->interpolate($expr, $args); + $this->wheres[$evaluatedExpr] = $evaluatedExpr; + } + return $this; + } + + /** + * Set one (or multiple) parameters to interpolate into the query. + * + * @param array|string $keys + * Key name, or an array of key-value pairs. + * @param null|mixed $value + * The new value of the parameter. + * Values may be strings, ints, or arrays thereof -- provided that the + * SQL query uses appropriate prefix (e.g. "@", "!", "#"). + * @return \CRM_Utils_SQL_Delete + */ + public function param($keys, $value = NULL) { + // Why bother with an override? To provide better type-hinting in `@return`. + return parent::param($keys, $value); + } + + /** + * @param array|NULL $parts + * List of fields to check (e.g. 'wheres'). + * Defaults to all. + * @return bool + */ + public function isEmpty($parts = NULL) { + $empty = TRUE; + $fields = [ + 'from', + 'wheres', + ]; + if ($parts !== NULL) { + $fields = array_intersect($fields, $parts); + } + foreach ($fields as $field) { + if (!empty($this->{$field})) { + $empty = FALSE; + } + } + return $empty; + } + + /** + * @return string + * SQL statement + */ + public function toSQL() { + $sql = 'DELETE '; + + if ($this->from !== NULL) { + $sql .= 'FROM ' . $this->from . "\n"; + } + if ($this->wheres) { + $sql .= 'WHERE (' . implode(') AND (', $this->wheres) . ")\n"; + } + if ($this->mode === self::INTERPOLATE_OUTPUT) { + $sql = $this->interpolate($sql, $this->params, self::INTERPOLATE_OUTPUT); + } + return $sql; + } + + /** + * Execute the query. + * + * To examine the results, use a function like `fetch()`, `fetchAll()`, + * `fetchValue()`, or `fetchMap()`. + * + * @param string|NULL $daoName + * The return object should be an instance of this class. + * Ex: 'CRM_Contact_BAO_Contact'. + * @param bool $i18nRewrite + * If the system has multilingual features, should the field/table + * names be rewritten? + * @return CRM_Core_DAO + * @see CRM_Core_DAO::executeQuery + * @see CRM_Core_I18n_Schema::rewriteQuery + */ + public function execute($daoName = NULL, $i18nRewrite = TRUE) { + // Don't pass through $params. toSQL() handles interpolation. + $params = []; + + // Don't pass through $abort, $trapException. Just use straight-up exceptions. + $abort = TRUE; + $trapException = FALSE; + $errorScope = CRM_Core_TemporaryErrorScope::useException(); + + // Don't pass through freeDAO. You can do it yourself. + $freeDAO = FALSE; + + return CRM_Core_DAO::executeQuery($this->toSQL(), $params, $abort, $daoName, + $freeDAO, $i18nRewrite, $trapException); + } + +} diff --git a/CRM/Utils/SQL/Insert.php b/CRM/Utils/SQL/Insert.php index fc8f13635830..816c89f605d0 100644 --- a/CRM/Utils/SQL/Insert.php +++ b/CRM/Utils/SQL/Insert.php @@ -36,6 +36,7 @@ class CRM_Utils_SQL_Insert { /** * Array list of column names + * @var array */ private $columns; @@ -59,10 +60,11 @@ public static function into($table) { */ public static function dao(CRM_Core_DAO $dao) { $table = CRM_Core_DAO::getLocaleTableName($dao->getTableName()); - $row = array(); + $row = []; foreach ((array) $dao as $key => $value) { if ($value === 'null') { - $value = NULL; // Blerg!!! + // Blerg!!! + $value = NULL; } // Skip '_foobar' and '{\u00}*_options' and 'N'. if (preg_match('/[a-zA-Z]/', $key{0}) && $key !== 'N') { @@ -80,7 +82,7 @@ public static function dao(CRM_Core_DAO $dao) { */ public function __construct($table) { $this->table = $table; - $this->rows = array(); + $this->rows = []; } /** @@ -128,11 +130,11 @@ public function row($row) { sort($columns); $this->columns = $columns; } - elseif (array_diff($this->columns, $columns) !== array()) { + elseif (array_diff($this->columns, $columns) !== []) { throw new CRM_Core_Exception("Inconsistent column names"); } - $escapedRow = array(); + $escapedRow = []; foreach ($this->columns as $column) { $escapedRow[$column] = $this->escapeString($row[$column]); } diff --git a/CRM/Utils/SQL/Select.php b/CRM/Utils/SQL/Select.php index 54128213d6c6..bdb892ed4d9e 100644 --- a/CRM/Utils/SQL/Select.php +++ b/CRM/Utils/SQL/Select.php @@ -1,9 +1,9 @@ where("foo_id=123")) * - Variable escaping is concise and controllable based on prefixes, eg * - similar to Drupal's t() @@ -79,57 +78,24 @@ * @endcode * * @package CRM - * @copyright CiviCRM LLC (c) 2004-2017 + * @copyright CiviCRM LLC (c) 2004-2019 */ -class CRM_Utils_SQL_Select implements ArrayAccess { +class CRM_Utils_SQL_Select extends CRM_Utils_SQL_BaseParamQuery { - /** - * Interpolate values as soon as they are passed in (where(), join(), etc). - * - * Default. - * - * Pro: Every clause has its own unique namespace for parameters. - * Con: Probably slower. - * Advice: Use this when aggregating SQL fragments from agents who - * maintained by different parties. - */ - const INTERPOLATE_INPUT = 'in'; - - /** - * Interpolate values when rendering SQL output (toSQL()). - * - * Pro: Probably faster. - * Con: Must maintain an aggregated list of all parameters. - * Advice: Use this when you have control over the entire query. - */ - const INTERPOLATE_OUTPUT = 'out'; - - /** - * Determine mode automatically. When the first attempt is made - * to use input-interpolation (eg `where(..., array(...))`) or - * output-interpolation (eg `param(...)`), the mode will be - * set. Subsequent calls will be validated using the same mode. - */ - const INTERPOLATE_AUTO = 'auto'; - - private $mode = NULL; private $insertInto = NULL; - private $insertIntoFields = array(); - private $selects = array(); + private $insertVerb = 'INSERT INTO '; + private $insertIntoFields = []; + private $selects = []; private $from; - private $joins = array(); - private $wheres = array(); - private $groupBys = array(); - private $havings = array(); - private $orderBys = array(); + private $joins = []; + private $wheres = []; + private $groupBys = []; + private $havings = []; + private $orderBys = []; private $limit = NULL; private $offset = NULL; - private $params = array(); private $distinct = NULL; - // Public to work-around PHP 5.3 limit. - public $strict = NULL; - /** * Create a new SELECT query. * @@ -138,7 +104,7 @@ class CRM_Utils_SQL_Select implements ArrayAccess { * @param array $options * @return CRM_Utils_SQL_Select */ - public static function from($from, $options = array()) { + public static function from($from, $options = []) { return new self($from, $options); } @@ -148,7 +114,7 @@ public static function from($from, $options = array()) { * @param array $options * @return CRM_Utils_SQL_Select */ - public static function fragment($options = array()) { + public static function fragment($options = []) { return new self(NULL, $options); } @@ -159,7 +125,7 @@ public static function fragment($options = array()) { * Table-name and optional alias. * @param array $options */ - public function __construct($from, $options = array()) { + public function __construct($from, $options = []) { $this->from = $from; $this->mode = isset($options['mode']) ? $options['mode'] : self::INTERPOLATE_AUTO; } @@ -176,7 +142,7 @@ public function copy() { /** * Merge something or other. * - * @param CRM_Utils_SQL_Select $other + * @param array|CRM_Utils_SQL_Select $other * @param array|NULL $parts * ex: 'joins', 'wheres' * @return CRM_Utils_SQL_Select @@ -186,6 +152,13 @@ public function merge($other, $parts = NULL) { return $this; } + if (is_array($other)) { + foreach ($other as $fragment) { + $this->merge($fragment, $parts); + } + return $this; + } + if ($this->mode === self::INTERPOLATE_AUTO) { $this->mode = $other->mode; } @@ -197,14 +170,14 @@ public function merge($other, $parts = NULL) { throw new RuntimeException("Cannot merge queries that use different interpolation modes ({$this->mode} vs {$other->mode})."); } - $arrayFields = array('insertIntoFields', 'selects', 'joins', 'wheres', 'groupBys', 'havings', 'orderBys', 'params'); + $arrayFields = ['insertIntoFields', 'selects', 'joins', 'wheres', 'groupBys', 'havings', 'orderBys', 'params']; foreach ($arrayFields as $f) { if ($parts === NULL || in_array($f, $parts)) { $this->{$f} = array_merge($this->{$f}, $other->{$f}); } } - $flatFields = array('insertInto', 'from', 'limit', 'offset'); + $flatFields = ['insertInto', 'from', 'limit', 'offset']; foreach ($flatFields as $f) { if ($parts === NULL || in_array($f, $parts)) { if ($other->{$f} !== NULL) { @@ -331,7 +304,7 @@ public function orderBy($exprs, $args = NULL, $weight = 0) { $exprs = (array) $exprs; foreach ($exprs as $expr) { $evaluatedExpr = $this->interpolate($expr, $args); - $this->orderBys[$evaluatedExpr] = array('value' => $evaluatedExpr, 'weight' => $weight, 'guid' => $guid++); + $this->orderBys[$evaluatedExpr] = ['value' => $evaluatedExpr, 'weight' => $weight, 'guid' => $guid++]; } return $this; } @@ -348,22 +321,8 @@ public function orderBy($exprs, $args = NULL, $weight = 0) { * @return \CRM_Utils_SQL_Select */ public function param($keys, $value = NULL) { - if ($this->mode === self::INTERPOLATE_AUTO) { - $this->mode = self::INTERPOLATE_OUTPUT; - } - elseif ($this->mode !== self::INTERPOLATE_OUTPUT) { - throw new RuntimeException("Select::param() only makes sense when interpolating on output."); - } - - if (is_array($keys)) { - foreach ($keys as $k => $v) { - $this->params[$k] = $v; - } - } - else { - $this->params[$keys] = $value; - } - return $this; + // Why bother with an override? To provide bett er type-hinting in `@return`. + return parent::param($keys, $value); } /** @@ -397,12 +356,39 @@ public function limit($limit, $offset = 0) { * @return CRM_Utils_SQL_Select * @see insertIntoField */ - public function insertInto($table, $fields = array()) { + public function insertInto($table, $fields = []) { $this->insertInto = $table; $this->insertIntoField($fields); return $this; } + /** + * Wrapper function of insertInto fn but sets insertVerb = "INSERT IGNORE INTO " + * + * @param string $table + * The name of the other table (which receives new data). + * @param array $fields + * The fields to fill in the other table (in order). + * @return CRM_Utils_SQL_Select + */ + public function insertIgnoreInto($table, $fields = []) { + $this->insertVerb = "INSERT IGNORE INTO "; + return $this->insertInto($table, $fields); + } + + /** + * Wrapper function of insertInto fn but sets insertVerb = "REPLACE INTO " + * + * @param string $table + * The name of the other table (which receives new data). + * @param array $fields + * The fields to fill in the other table (in order). + */ + public function replaceInto($table, $fields = []) { + $this->insertVerb = "REPLACE INTO "; + return $this->insertInto($table, $fields); + } + /** * @param array $fields * The fields to fill in the other table (in order). @@ -424,7 +410,7 @@ public function insertIntoField($fields) { */ public function isEmpty($parts = NULL) { $empty = TRUE; - $fields = array( + $fields = [ 'insertInto', 'insertIntoFields', 'selects', @@ -436,7 +422,7 @@ public function isEmpty($parts = NULL) { 'orderBys', 'limit', 'offset', - ); + ]; if ($parts !== NULL) { $fields = array_intersect($fields, $parts); } @@ -448,101 +434,6 @@ public function isEmpty($parts = NULL) { return $empty; } - /** - * Enable (or disable) strict mode. - * - * In strict mode, unknown variables will generate exceptions. - * - * @param bool $strict - * @return CRM_Utils_SQL_Select - */ - public function strict($strict = TRUE) { - $this->strict = $strict; - return $this; - } - - /** - * Given a string like "field_name = @value", replace "@value" with an escaped SQL string - * - * @param string $expr SQL expression - * @param null|array $args a list of values to insert into the SQL expression; keys are prefix-coded: - * prefix '@' => escape SQL - * prefix '#' => literal number, skip escaping but do validation - * prefix '!' => literal, skip escaping and validation - * if a value is an array, then it will be imploded - * - * PHP NULL's will be treated as SQL NULL's. The PHP string "null" will be treated as a string. - * - * @param string $activeMode - * - * @return string - */ - public function interpolate($expr, $args, $activeMode = self::INTERPOLATE_INPUT) { - if ($args === NULL) { - return $expr; - } - else { - if ($this->mode === self::INTERPOLATE_AUTO) { - $this->mode = $activeMode; - } - elseif ($activeMode !== $this->mode) { - throw new RuntimeException("Cannot mix interpolation modes."); - } - - $select = $this; - return preg_replace_callback('/([#!@])([a-zA-Z0-9_]+)/', function($m) use ($select, $args) { - if (isset($args[$m[2]])) { - $values = $args[$m[2]]; - } - elseif (isset($args[$m[1] . $m[2]])) { - // Backward compat. Keys in $args look like "#myNumber" or "@myString". - $values = $args[$m[1] . $m[2]]; - } - elseif ($select->strict) { - throw new CRM_Core_Exception('Cannot build query. Variable "' . $m[1] . $m[2] . '" is unknown.'); - } - else { - // Unrecognized variables are ignored. Mitigate risk of accidents. - return $m[0]; - } - $values = is_array($values) ? $values : array($values); - switch ($m[1]) { - case '@': - $parts = array_map(array($select, 'escapeString'), $values); - return implode(', ', $parts); - - // TODO: ensure all uses of this un-escaped literal are safe - case '!': - return implode(', ', $values); - - case '#': - foreach ($values as $valueKey => $value) { - if ($value === NULL) { - $values[$valueKey] = 'NULL'; - } - elseif (!is_numeric($value)) { - //throw new API_Exception("Failed encoding non-numeric value" . var_export(array($m[0] => $values), TRUE)); - throw new CRM_Core_Exception("Failed encoding non-numeric value (" . $m[0] . ")"); - } - } - return implode(', ', $values); - - default: - throw new CRM_Core_Exception("Unrecognized prefix"); - } - }, $expr); - } - } - - /** - * @param string|NULL $value - * @return string - * SQL expression, e.g. "it\'s great" (with-quotes) or NULL (without-quotes) - */ - public function escapeString($value) { - return $value === NULL ? 'NULL' : '"' . CRM_Core_DAO::escapeString($value) . '"'; - } - /** * @return string * SQL statement @@ -550,10 +441,11 @@ public function escapeString($value) { public function toSQL() { $sql = ''; if ($this->insertInto) { - $sql .= 'INSERT INTO ' . $this->insertInto . ' ('; + $sql .= $this->insertVerb . $this->insertInto . ' ('; $sql .= implode(', ', $this->insertIntoFields); $sql .= ")\n"; } + if ($this->selects) { $sql .= 'SELECT ' . $this->distinct . implode(', ', $this->selects) . "\n"; } @@ -577,7 +469,7 @@ public function toSQL() { } if ($this->orderBys) { $orderBys = CRM_Utils_Array::crmArraySortByField($this->orderBys, - array('weight', 'guid')); + ['weight', 'guid']); $orderBys = CRM_Utils_Array::collect('value', $orderBys); $sql .= 'ORDER BY ' . implode(', ', $orderBys) . "\n"; } @@ -594,64 +486,35 @@ public function toSQL() { } /** - * Has an offset been set. - * - * @param string $offset - * - * @return bool + * Execute the query. + * + * To examine the results, use a function like `fetch()`, `fetchAll()`, + * `fetchValue()`, or `fetchMap()`. + * + * @param string|NULL $daoName + * The return object should be an instance of this class. + * Ex: 'CRM_Contact_BAO_Contact'. + * @param bool $i18nRewrite + * If the system has multilingual features, should the field/table + * names be rewritten? + * @return CRM_Core_DAO + * @see CRM_Core_DAO::executeQuery + * @see CRM_Core_I18n_Schema::rewriteQuery */ - public function offsetExists($offset) { - return isset($this->params[$offset]); - } + public function execute($daoName = NULL, $i18nRewrite = TRUE) { + // Don't pass through $params. toSQL() handles interpolation. + $params = []; - /** - * Get the value of a SQL parameter. - * - * @code - * $select['cid'] = 123; - * $select->where('contact.id = #cid'); - * echo $select['cid']; - * @endCode - * - * @param string $offset - * @return mixed - * @see param() - * @see ArrayAccess::offsetGet - */ - public function offsetGet($offset) { - return $this->params[$offset]; - } + // Don't pass through $abort, $trapException. Just use straight-up exceptions. + $abort = TRUE; + $trapException = FALSE; + $errorScope = CRM_Core_TemporaryErrorScope::useException(); - /** - * Set the value of a SQL parameter. - * - * @code - * $select['cid'] = 123; - * $select->where('contact.id = #cid'); - * echo $select['cid']; - * @endCode - * - * @param string $offset - * @param mixed $value - * The new value of the parameter. - * Values may be strings, ints, or arrays thereof -- provided that the - * SQL query uses appropriate prefix (e.g. "@", "!", "#"). - * @see param() - * @see ArrayAccess::offsetSet - */ - public function offsetSet($offset, $value) { - $this->param($offset, $value); - } + // Don't pass through freeDAO. You can do it yourself. + $freeDAO = FALSE; - /** - * Unset the value of a SQL parameter. - * - * @param string $offset - * @see param() - * @see ArrayAccess::offsetUnset - */ - public function offsetUnset($offset) { - unset($this->params[$offset]); + return CRM_Core_DAO::executeQuery($this->toSQL(), $params, $abort, $daoName, + $freeDAO, $i18nRewrite, $trapException); } } diff --git a/CRM/Utils/SQL/TempTable.php b/CRM/Utils/SQL/TempTable.php new file mode 100644 index 000000000000..ad4cc452542f --- /dev/null +++ b/CRM/Utils/SQL/TempTable.php @@ -0,0 +1,334 @@ +getName(); + * $name = CRM_Utils_SQL_TempTable::build()->setDurable()->getName(); + * $name = CRM_Utils_SQL_TempTable::build()->setCategory('contactstats')->setId($contact['id'])->getName(); + * + * Example 2: Create a temp table using the results of a SELECT query. + * + * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery('SELECT id, display_name FROM civicrm_contact'); + * $tmpTbl = CRM_Utils_SQL_TempTable::build()->createWithQuery(CRM_Utils_SQL_Select::from('civicrm_contact')->select('display_name')); + * + * Example 3: Create an empty temp table with list of columns. + * + * $tmpTbl = CRM_Utils_SQL_TempTable::build()->setDurable()->createWithColumns('id int(10, name varchar(64)'); + * + * Example 4: Drop a table that you previously created. + * + * $tmpTbl->drop(); + * + * Example 5: Auto-drop a temp table when $tmpTbl falls out of scope + * + * $tmpTbl->setAutodrop(); + * + */ +class CRM_Utils_SQL_TempTable { + + const UTF8 = 'DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci'; + const CATEGORY_LENGTH = 12; + const CATEGORY_REGEXP = ';^[a-zA-Z0-9]+$;'; + // MAX{64} - CATEGORY_LENGTH{12} - CONST_LENGHTH{15} = 37 + const ID_LENGTH = 37; + const ID_REGEXP = ';^[a-zA-Z0-9_]+$;'; + const INNODB = 'ENGINE=InnoDB'; + const MEMORY = 'ENGINE=MEMORY'; + + /** + * @var bool + */ + protected $durable; + + /** + * @var bool + */ + protected $utf8; + + protected $category; + + protected $id; + + protected $autodrop; + + protected $memory; + + protected $createSql; + + /** + * @return CRM_Utils_SQL_TempTable + */ + public static function build() { + $t = new CRM_Utils_SQL_TempTable(); + $t->category = NULL; + $t->id = md5(uniqid('', TRUE)); + // The constant CIVICRM_TEMP_FORCE_DURABLE is for local debugging. + $t->durable = CRM_Utils_Constant::value('CIVICRM_TEMP_FORCE_DURABLE', FALSE); + $t->utf8 = TRUE; + $t->autodrop = FALSE; + $t->memory = FALSE; + return $t; + } + + public function __destruct() { + if ($this->autodrop) { + $this->drop(); + } + } + + /** + * Determine the full table name. + * + * @return string + * Ex: 'civicrm_tmp_d_foo_abcd1234abcd1234' + */ + public function getName() { + $parts = ['civicrm', 'tmp']; + $parts[] = ($this->durable ? 'd' : 'e'); + $parts[] = $this->category ? $this->category : 'dflt'; + $parts[] = $this->id ? $this->id : 'dflt'; + return implode('_', $parts); + } + + /** + * Create the table using results from a SELECT query. + * + * @param string|CRM_Utils_SQL_Select $selectQuery + * @return CRM_Utils_SQL_TempTable + */ + public function createWithQuery($selectQuery) { + $sql = sprintf('%s %s %s AS %s', + $this->toSQL('CREATE'), + $this->memory ? self::MEMORY : self::INNODB, + $this->utf8 ? self::UTF8 : '', + ($selectQuery instanceof CRM_Utils_SQL_Select ? $selectQuery->toSQL() : $selectQuery) + ); + CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE); + $this->createSql = $sql; + return $this; + } + + /** + * Create the empty table. + * + * @parma string $columns + * SQL column listing. + * Ex: 'id int(10), name varchar(64)'. + * @return CRM_Utils_SQL_TempTable + */ + public function createWithColumns($columns) { + $sql = sprintf('%s (%s) %s %s', + $this->toSQL('CREATE'), + $columns, + $this->memory ? self::MEMORY : self::INNODB, + $this->utf8 ? self::UTF8 : '' + ); + CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE); + $this->createSql = $sql; + return $this; + } + + /** + * Drop the table. + * + * @return CRM_Utils_SQL_TempTable + */ + public function drop() { + $sql = $this->toSQL('DROP', 'IF EXISTS'); + CRM_Core_DAO::executeQuery($sql, [], TRUE, NULL, TRUE, FALSE); + return $this; + } + + /** + * @param string $action + * Ex: 'CREATE', 'DROP' + * @param string|NULL $ifne + * Ex: 'IF EXISTS', 'IF NOT EXISTS'. + * @return string + * Ex: 'CREATE TEMPORARY TABLE `civicrm_tmp_e_foo_abcd1234`' + * Ex: 'CREATE TABLE IF NOT EXISTS `civicrm_tmp_d_foo_abcd1234`' + */ + private function toSQL($action, $ifne = NULL) { + $parts = []; + $parts[] = $action; + if (!$this->durable) { + $parts[] = 'TEMPORARY'; + } + $parts[] = 'TABLE'; + if ($ifne) { + $parts[] = $ifne; + } + $parts[] = '`' . $this->getName() . '`'; + return implode(' ', $parts); + } + + /** + * @return string|NULL + */ + public function getCategory() { + return $this->category; + } + + /** + * @return string|NULL + */ + public function getId() { + return $this->id; + } + + /** + * @return string|NULL + */ + public function getCreateSql() { + return $this->createSql; + } + + /** + * @return bool + */ + public function isAutodrop() { + return $this->autodrop; + } + + /** + * @return bool + */ + public function isDurable() { + return $this->durable; + } + + /** + * @return bool + */ + public function isMemory() { + return $this->memory; + } + + /** + * @return bool + */ + public function isUtf8() { + return $this->utf8; + } + + /** + * @param bool $autodrop + * @return CRM_Utils_SQL_TempTable + */ + public function setAutodrop($autodrop = TRUE) { + $this->autodrop = $autodrop; + return $this; + } + + /** + * @param string|NULL $category + * + * @return CRM_Utils_SQL_TempTable + */ + public function setCategory($category) { + if ($category && !preg_match(self::CATEGORY_REGEXP, $category) || strlen($category) > self::CATEGORY_LENGTH) { + throw new \RuntimeException("Malformed temp table category"); + } + $this->category = $category; + return $this; + } + + /** + * Set whether the table should be durable. + * + * Durable tables are not TEMPORARY in the mysql sense. + * + * @param bool $durable + * + * @return CRM_Utils_SQL_TempTable + */ + public function setDurable($durable = TRUE) { + $this->durable = $durable; + return $this; + } + + /** + * Setter for id + * + * @param mixed $id + * + * @return CRM_Utils_SQL_TempTable + */ + public function setId($id) { + if ($id && !preg_match(self::ID_REGEXP, $id) || strlen($id) > self::ID_LENGTH) { + throw new \RuntimeException("Malformed temp table id"); + } + $this->id = $id; + return $this; + } + + /** + * Set table engine to MEMORY. + * + * @param bool $value + * + * @return $this + */ + public function setMemory($value = TRUE) { + $this->memory = $value; + return $this; + } + + /** + * Set table collation to UTF8. + * + * @deprecated This method is deprecated as tables should be assumed to have + * UTF-8 as the default character set and collation; some other character set + * or collation may be specified in the column definition. + * + * @param bool $value + * + * @return $this + */ + public function setUtf8($value = TRUE) { + $this->utf8 = $value; + return $this; + } + +} diff --git a/CRM/Utils/Signer.php b/CRM/Utils/Signer.php index bd825d0a6ed7..6323b8931d7d 100644 --- a/CRM/Utils/Signer.php +++ b/CRM/Utils/Signer.php @@ -1,9 +1,9 @@ secret = $secret; $this->paramNames = $paramNames; - $this->signDelim = "_"; // chosen to be valid in URLs but not in salt or md5 + // chosen to be valid in URLs but not in salt or md5 + $this->signDelim = "_"; $this->defaultSalt = CRM_Utils_String::createRandom(self::SALT_LEN, CRM_Utils_String::ALPHANUMERIC); } @@ -81,9 +83,9 @@ public function __construct($secret, $paramNames) { * @return string, the full public token representing the signature */ public function sign($params, $salt = NULL) { - $message = array(); + $message = []; $message['secret'] = $this->secret; - $message['payload'] = array(); + $message['payload'] = []; if (empty($salt)) { $message['salt'] = $this->createSalt(); } diff --git a/CRM/Utils/SoapServer.php b/CRM/Utils/SoapServer.php index 5ab5672c3446..395cd549ebd9 100644 --- a/CRM/Utils/SoapServer.php +++ b/CRM/Utils/SoapServer.php @@ -1,9 +1,9 @@ verify($key); - $params = array( + $params = [ 'job_id' => $job, 'time_stamp' => date('YmdHis'), 'event_queue_id' => $queue, 'hash' => $hash, 'body' => $body, 'version' => 3, - ); + ]; $result = civicrm_api('Mailing', 'event_bounce', $params); return CRM_Utils_Array::encode_items($result); } @@ -174,14 +176,14 @@ public function mailer_event_bounce($key, $job, $queue, $hash, $body) { */ public function mailer_event_unsubscribe($key, $job, $queue, $hash) { $this->verify($key); - $params = array( + $params = [ 'job_id' => $job, 'time_stamp' => date('YmdHis'), 'org_unsubscribe' => 0, 'event_queue_id' => $queue, 'hash' => $hash, 'version' => 3, - ); + ]; $result = civicrm_api('MailingGroup', 'event_unsubscribe', $params); return CRM_Utils_Array::encode_items($result); } @@ -197,14 +199,14 @@ public function mailer_event_unsubscribe($key, $job, $queue, $hash) { */ public function mailer_event_domain_unsubscribe($key, $job, $queue, $hash) { $this->verify($key); - $params = array( + $params = [ 'job_id' => $job, 'time_stamp' => date('YmdHis'), 'org_unsubscribe' => 1, 'event_queue_id' => $queue, 'hash' => $hash, 'version' => 3, - ); + ]; $result = civicrm_api('MailingGroup', 'event_domain_unsubscribe', $params); return CRM_Utils_Array::encode_items($result); } @@ -220,14 +222,14 @@ public function mailer_event_domain_unsubscribe($key, $job, $queue, $hash) { */ public function mailer_event_resubscribe($key, $job, $queue, $hash) { $this->verify($key); - $params = array( + $params = [ 'job_id' => $job, 'time_stamp' => date('YmdHis'), 'org_unsubscribe' => 0, 'event_queue_id' => $queue, 'hash' => $hash, 'version' => 3, - ); + ]; $result = civicrm_api('MailingGroup', 'event_resubscribe', $params); return CRM_Utils_Array::encode_items($result); } @@ -243,11 +245,11 @@ public function mailer_event_resubscribe($key, $job, $queue, $hash) { */ public function mailer_event_subscribe($key, $email, $domain, $group) { $this->verify($key); - $params = array( + $params = [ 'email' => $email, 'group_id' => $group, 'version' => 3, - ); + ]; $result = civicrm_api('MailingGroup', 'event_subscribe', $params); return CRM_Utils_Array::encode_items($result); } @@ -263,14 +265,14 @@ public function mailer_event_subscribe($key, $email, $domain, $group) { */ public function mailer_event_confirm($key, $contact, $subscribe, $hash) { $this->verify($key); - $params = array( + $params = [ 'contact_id' => $contact, 'subscribe_id' => $subscribe, 'time_stamp' => date('YmdHis'), 'event_subscribe_id' => $subscribe, 'hash' => $hash, 'version' => 3, - ); + ]; $result = civicrm_api('Mailing', 'event_confirm', $params); return CRM_Utils_Array::encode_items($result); } @@ -290,7 +292,7 @@ public function mailer_event_confirm($key, $contact, $subscribe, $hash) { */ public function mailer_event_reply($key, $job, $queue, $hash, $bodyTxt, $rt, $bodyHTML = NULL, $fullEmail = NULL) { $this->verify($key); - $params = array( + $params = [ 'job_id' => $job, 'event_queue_id' => $queue, 'hash' => $hash, @@ -300,7 +302,7 @@ public function mailer_event_reply($key, $job, $queue, $hash, $bodyTxt, $rt, $bo 'fullEmail' => $fullEmail, 'time_stamp' => date('YmdHis'), 'version' => 3, - ); + ]; $result = civicrm_api('Mailing', 'event_reply', $params); return CRM_Utils_Array::encode_items($result); } @@ -317,13 +319,13 @@ public function mailer_event_reply($key, $job, $queue, $hash, $bodyTxt, $rt, $bo */ public function mailer_event_forward($key, $job, $queue, $hash, $email) { $this->verify($key); - $params = array( + $params = [ 'job_id' => $job, 'event_queue_id' => $queue, 'hash' => $hash, 'email' => $email, 'version' => 3, - ); + ]; $result = civicrm_api('Mailing', 'event_forward', $params); return CRM_Utils_Array::encode_items($result); } diff --git a/CRM/Utils/Sort.php b/CRM/Utils/Sort.php index 991cced62a1e..a844d3d2d3ea 100644 --- a/CRM/Utils/Sort.php +++ b/CRM/Utils/Sort.php @@ -1,9 +1,9 @@ _vars = array(); - $this->_response = array(); + $this->_vars = []; + $this->_response = []; foreach ($vars as $weight => $value) { - $this->_vars[$weight] = array( + $this->_vars[$weight] = [ 'name' => CRM_Utils_Type::validate($value['sort'], 'MysqlColumnNameOrAlias'), 'direction' => CRM_Utils_Array::value('direction', $value), 'title' => $value['name'], - ); + ]; } $this->_currentSortID = 1; @@ -219,12 +219,12 @@ public function initSortID($defaultSortOrder) { public function initialize($defaultSortOrder) { $this->initSortID($defaultSortOrder); - $this->_response = array(); + $this->_response = []; $current = $this->_currentSortID; foreach ($this->_vars as $index => $item) { $name = $item['name']; - $this->_response[$name] = array(); + $this->_response[$name] = []; $newDirection = ($item['direction'] == self::ASCENDING) ? self::DESCENDING : self::ASCENDING; @@ -274,7 +274,7 @@ public function getCurrentSortDirection() { * (-1 or 1) */ public static function cmpFunc($a, $b) { - $cmp_order = array('weight', 'id', 'title', 'name'); + $cmp_order = ['weight', 'id', 'title', 'name']; foreach ($cmp_order as $attribute) { if (isset($a[$attribute]) && isset($b[$attribute])) { if ($a[$attribute] < $b[$attribute]) { diff --git a/CRM/Utils/String.php b/CRM/Utils/String.php index fc28c0f000b7..d0f295dd1804 100644 --- a/CRM/Utils/String.php +++ b/CRM/Utils/String.php @@ -1,9 +1,9 @@ 'Acl', 'ACL' => 'Acl', 'im' => 'Im', 'IM' => 'Im', - ); + ]; if (isset($map[$string])) { return $map[$string]; } @@ -120,8 +120,12 @@ public static function convertStringToCamel($string) { $fragments = explode('_', $string); foreach ($fragments as & $fragment) { $fragment = ucfirst($fragment); + // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in without underscores) + if (strpos($fragment, 'Uf') === 0 && strlen($string) > 2) { + $fragment = 'UF' . ucfirst(substr($fragment, 2)); + } } - // Special case: UFGroup, UFJoin, UFMatch, UFField + // Special case: UFGroup, UFJoin, UFMatch, UFField (if passed in underscore-separated) if ($fragments[0] === 'Uf') { $fragments[0] = 'UF'; } @@ -158,7 +162,7 @@ public static function rename($name, $len = 4) { * The last component */ public static function getClassName($string, $char = '_') { - $names = array(); + $names = []; if (!is_array($string)) { $names = explode($char, $string); } @@ -241,7 +245,7 @@ public static function isAscii($str, $utf8 = TRUE) { return TRUE; } else { - $order = array('ASCII'); + $order = ['ASCII']; if ($utf8) { $order[] = 'UTF-8'; } @@ -265,7 +269,7 @@ public static function isAscii($str, $utf8 = TRUE) { public static function regex($str, $regexRules) { // redact the regular expressions if (!empty($regexRules) && isset($str)) { - static $matches, $totalMatches, $match = array(); + static $matches, $totalMatches, $match = []; foreach ($regexRules as $pattern => $replacement) { preg_match_all($pattern, $str, $matches); if (!empty($matches[0])) { @@ -291,7 +295,7 @@ public static function regex($str, $regexRules) { } return $match; } - return CRM_Core_DAO::$_nullArray; + return []; } /** @@ -333,7 +337,7 @@ public static function isUtf8($str) { // iconv('ISO-8859-1', 'UTF-8', $str); } else { - $enc = mb_detect_encoding($str, array('UTF-8'), TRUE); + $enc = mb_detect_encoding($str, ['UTF-8'], TRUE); return ($enc !== FALSE); } } @@ -513,7 +517,7 @@ public static function &makeArray($string) { $string = trim($string); $values = explode("\n", $string); - $result = array(); + $result = []; foreach ($values as $value) { list($n, $v) = CRM_Utils_System::explode('=', $value, 2); if (!empty($v)) { @@ -534,7 +538,7 @@ public static function &makeArray($string) { * only the first alternative found (or the text without alternatives) */ public static function stripAlternatives($full) { - $matches = array(); + $matches = []; preg_match('/-ALTERNATIVE ITEM 0-(.*?)-ALTERNATIVE ITEM 1-.*-ALTERNATIVE END-/s', $full, $matches); if (isset($matches[1]) && @@ -588,7 +592,7 @@ public static function stripPathChars( } if ($_searchChars == NULL) { - $_searchChars = array( + $_searchChars = [ '&', ';', ',', @@ -606,7 +610,7 @@ public static function stripPathChars( "\r\n", "\n", "\t", - ); + ]; $_replaceChar = '_'; } @@ -621,7 +625,6 @@ public static function stripPathChars( return str_replace($search, $replace, $string); } - /** * Use HTMLPurifier to clean up a text string and remove any potential * xss attacks. This is primarily used in public facing pages which @@ -638,6 +641,7 @@ public static function purifyHTML($string) { if (!$_filter) { $config = HTMLPurifier_Config::createDefault(); $config->set('Core.Encoding', 'UTF-8'); + $config->set('Attr.AllowedFrameTargets', ['_blank', '_self', '_parent', '_top']); // Disable the cache entirely $config->set('Cache.DefinitionImpl', NULL); @@ -657,18 +661,10 @@ public static function purifyHTML($string) { * @return string */ public static function ellipsify($string, $maxLen) { - $len = strlen($string); - if ($len <= $maxLen) { + if (mb_strlen($string, 'UTF-8') <= $maxLen) { return $string; } - else { - $end = $maxLen - 3; - while (strlen($string) > $maxLen - 3) { - $string = mb_substr($string, 0, $end, 'UTF-8'); - $end = $end - 1; - } - return $string . '...'; - } + return mb_substr($string, 0, $maxLen - 3, 'UTF-8') . '...'; } /** @@ -703,10 +699,10 @@ public static function createRandom($len, $alphabet) { public static function parsePrefix($delim, $string, $defaultPrefix = NULL) { $pos = strpos($string, $delim); if ($pos === FALSE) { - return array($defaultPrefix, $string); + return [$defaultPrefix, $string]; } else { - return array(substr($string, 0, $pos), substr($string, 1 + $pos)); + return [substr($string, 0, $pos), substr($string, 1 + $pos)]; } } @@ -789,6 +785,80 @@ public static function unstupifyUrl($htmlUrl) { return str_replace('&', '&', $htmlUrl); } + /** + * When a user supplies a URL (e.g. to an image), we'd like to: + * - Remove the protocol and domain name if the URL points to the current + * site. + * - Keep the domain name for remote URLs. + * - Optionally, force remote URLs to use https instead of http (which is + * useful for images) + * + * @param string $url + * The URL to simplify. Examples: + * "https://example.org/sites/default/files/coffee-mug.jpg" + * "sites/default/files/coffee-mug.jpg" + * "http://i.stack.imgur.com/9jb2ial01b.png" + * @param bool $forceHttps = FALSE + * If TRUE, ensure that remote URLs use https. If a URL with + * http is supplied, then we'll change it to https. + * This is useful for situations like showing a premium product on a + * contribution, because (as reported in CRM-14283) if the user gets a + * browser warning like "page contains insecure elements" on a contribution + * page, that's a very bad thing. Thus, even if changing http to https + * breaks the image, that's better than leaving http content in a + * contribution page. + * + * @return string + * The simplified URL. Examples: + * "/sites/default/files/coffee-mug.jpg" + * "https://i.stack.imgur.com/9jb2ial01b.png" + */ + public static function simplifyURL($url, $forceHttps = FALSE) { + $config = CRM_Core_Config::singleton(); + $siteURLParts = self::simpleParseUrl($config->userFrameworkBaseURL); + $urlParts = self::simpleParseUrl($url); + + // If the image is locally hosted, then only give the path to the image + $urlIsLocal + = ($urlParts['host+port'] == '') + | ($urlParts['host+port'] == $siteURLParts['host+port']); + if ($urlIsLocal) { + // and make sure it begins with one forward slash + return preg_replace('_^/*(?=.)_', '/', $urlParts['path+query']); + } + + // If the URL is external, then keep the full URL as supplied + else { + return $forceHttps ? preg_replace('_^http://_', 'https://', $url) : $url; + } + } + + /** + * A simplified version of PHP's parse_url() function. + * + * @param string $url + * e.g. "https://example.com:8000/foo/bar/?id=1#fragment" + * + * @return array + * Will always contain keys 'host+port' and 'path+query', even if they're + * empty strings. Example: + * [ + * 'host+port' => "example.com:8000", + * 'path+query' => "/foo/bar/?id=1", + * ] + */ + public static function simpleParseUrl($url) { + $parts = parse_url($url); + $host = isset($parts['host']) ? $parts['host'] : ''; + $port = isset($parts['port']) ? ':' . $parts['port'] : ''; + $path = isset($parts['path']) ? $parts['path'] : ''; + $query = isset($parts['query']) ? '?' . $parts['query'] : ''; + return [ + 'host+port' => "$host$port", + 'path+query' => "$path$query", + ]; + } + /** * Formats a string of attributes for insertion in an html tag. * @@ -847,7 +917,7 @@ public static function endsWith($string, $fragment) { */ public static function filterByWildcards($patterns, $allStrings, $allowNew = FALSE) { $patterns = (array) $patterns; - $result = array(); + $result = []; foreach ($patterns as $pattern) { if (!\CRM_Utils_String::endsWith($pattern, '*')) { if ($allowNew || in_array($pattern, $allStrings)) { diff --git a/CRM/Utils/System.php b/CRM/Utils/System.php index bee39e8e0bec..b52932368805 100644 --- a/CRM/Utils/System.php +++ b/CRM/Utils/System.php @@ -1,9 +1,9 @@ userSystem; - return call_user_func_array(array($userSystem, $name), $arguments); + return call_user_func_array([$userSystem, $name], $arguments); } /** @@ -103,8 +105,7 @@ public static function makeURL($urlVar, $includeReset = FALSE, $includeForce = T } } - return - self::url( + return self::url( $path, CRM_Utils_System::getLinksUrl($urlVar, $includeReset, $includeForce), $absolute @@ -133,9 +134,9 @@ public static function makeURL($urlVar, $includeReset = FALSE, $includeForce = T */ public static function getLinksUrl($urlVar, $includeReset = FALSE, $includeForce = TRUE, $skipUFVar = TRUE) { // Sort out query string to prevent messy urls - $querystring = array(); - $qs = array(); - $arrays = array(); + $querystring = []; + $qs = []; + $arrays = []; if (!empty($_SERVER['QUERY_STRING'])) { $qs = explode('&', str_replace('&', '&', $_SERVER['QUERY_STRING'])); @@ -215,8 +216,7 @@ public static function theme( $print = FALSE, $maintenance = FALSE ) { - $config = &CRM_Core_Config::singleton(); - return $config->userSystem->theme($content, $print, $maintenance); + return CRM_Core_Config::singleton()->userSystem->theme($content, $print, $maintenance); } /** @@ -433,8 +433,10 @@ public static function getClassName($object) { * * @param string $url * The URL to provide to the browser via the Location header. + * @param array $context + * Optional additional information for the hook. */ - public static function redirect($url = NULL) { + public static function redirect($url = NULL, $context = []) { if (!$url) { $url = self::url('civicrm/dashboard', 'reset=1'); } @@ -442,12 +444,18 @@ public static function redirect($url = NULL) { // this is kinda hackish but not sure how to do it right $url = str_replace('&', '&', $url); + $context['output'] = CRM_Utils_Array::value('snippet', $_GET); + + $parsedUrl = CRM_Utils_Url::parseUrl($url); + CRM_Utils_Hook::alterRedirect($parsedUrl, $context); + $url = CRM_Utils_Url::unparseUrl($parsedUrl); + // If we are in a json context, respond appropriately - if (CRM_Utils_Array::value('snippet', $_GET) === 'json') { - CRM_Core_Page_AJAX::returnJsonResponse(array( + if ($context['output'] === 'json') { + CRM_Core_Page_AJAX::returnJsonResponse([ 'status' => 'redirect', 'userContext' => $url, - )); + ]); } self::setHttpHeader('Location', $url); @@ -619,7 +627,7 @@ public static function authenticateScript($abort = TRUE, $name = NULL, $pass = N list($userID, $ufID, $randomNumber) = $result; if ($userID && $ufID) { $config = CRM_Core_Config::singleton(); - $config->userSystem->setUserSession(array($userID, $ufID)); + $config->userSystem->setUserSession([$userID, $ufID]); } else { return self::authenticateAbort( @@ -680,7 +688,6 @@ public static function setUFMessage($message) { return $config->userSystem->setMessage($message); } - /** * Determine whether a value is null-ish. * @@ -741,7 +748,7 @@ private static function parsePHPModules() { $s = preg_replace('/]*>([^<]+)<\/th>/', "\\1", $s); $s = preg_replace('/]*>([^<]+)<\/td>/', "\\1", $s); $vTmp = preg_split('/(

    [^<]+<\/h2>)/', $s, -1, PREG_SPLIT_DELIM_CAPTURE); - $vModules = array(); + $vModules = []; for ($i = 1; $i < count($vTmp); $i++) { if (preg_match('/

    ([^<]+)<\/h2>/', $vTmp[$i], $vMat)) { $vName = trim($vMat[1]); @@ -752,7 +759,7 @@ private static function parsePHPModules() { $vPat2 = "/$vPat\s*$vPat/"; // 3cols if (preg_match($vPat3, $vOne, $vMat)) { - $vModules[$vName][trim($vMat[1])] = array(trim($vMat[2]), trim($vMat[3])); + $vModules[$vName][trim($vMat[1])] = [trim($vMat[2]), trim($vMat[3])]; // 2cols } elseif (preg_match($vPat2, $vOne, $vMat)) { @@ -821,7 +828,7 @@ public static function download( self::setHttpHeader('Expires', $now); // lem9 & loic1: IE needs specific headers - $isIE = strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE'); + $isIE = empty($_SERVER['HTTP_USER_AGENT']) ? FALSE : strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE'); if ($ext) { $fileString = "filename=\"{$name}.{$ext}\""; } @@ -899,7 +906,7 @@ public static function fixURL($url) { */ public static function validCallback($callback) { if (self::$_callbacks === NULL) { - self::$_callbacks = array(); + self::$_callbacks = []; } if (!array_key_exists($callback, self::$_callbacks)) { @@ -998,7 +1005,7 @@ public static function checkPHPVersion($ver = 5, $abort = TRUE) { if ($abort) { CRM_Core_Error::fatal(ts('This feature requires PHP Version %1 or greater', - array(1 => $ver) + [1 => $ver] )); } return FALSE; @@ -1074,25 +1081,12 @@ public static function version() { if (!$version) { $verFile = implode(DIRECTORY_SEPARATOR, - array(dirname(__FILE__), '..', '..', 'civicrm-version.php') + [dirname(__FILE__), '..', '..', 'xml', 'version.xml'] ); if (file_exists($verFile)) { - require_once $verFile; - if (function_exists('civicrmVersion')) { - $info = civicrmVersion(); - $version = $info['version']; - } - } - else { - // svn installs don't have version.txt by default. In that case version.xml should help - - $verFile = implode(DIRECTORY_SEPARATOR, - array(dirname(__FILE__), '..', '..', 'xml', 'version.xml') - ); - if (file_exists($verFile)) { - $str = file_get_contents($verFile); - $xmlObj = simplexml_load_string($str); - $version = (string) $xmlObj->version_no; - } + $str = file_get_contents($verFile); + $xmlObj = simplexml_load_string($str); + $version = (string) $xmlObj->version_no; } // pattern check @@ -1136,7 +1130,7 @@ public static function getAllHeaders() { // emulate get all headers // http://www.php.net/manual/en/function.getallheaders.php#66335 - $headers = array(); + $headers = []; foreach ($_SERVER as $name => $value) { if (substr($name, 0, 5) == 'HTTP_') { $headers[str_replace(' ', @@ -1173,8 +1167,7 @@ public static function getRequestHeaders() { * this function, please go and change the code in the install script as well. */ public static function isSSL() { - return - (isset($_SERVER['HTTPS']) && + return (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') ? TRUE : FALSE; } @@ -1196,6 +1189,8 @@ public static function redirectToSSL($abort = FALSE) { ) { // ensure that SSL is enabled on a civicrm url (for cookie reasons etc) $url = "https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; + // @see https://lab.civicrm.org/dev/core/issues/425 if you're seeing this message. + Civi::log()->warning('CiviCRM thinks site is not SSL, redirecting to {url}', ['url' => $url]); if (!self::checkURL($url, TRUE)) { if ($abort) { CRM_Core_Error::fatal('HTTPS is not set up on this machine'); @@ -1320,14 +1315,14 @@ public static function docURL2($page, $URLonly = FALSE, $text = NULL, $title = N return $docBaseURL . str_replace(' ', '+', $page); } else { - $params = array( + $params = [ 'page' => $page, 'URLonly' => $URLonly, 'text' => $text, 'title' => $title, 'style' => $style, 'resource' => $resource, - ); + ]; return self::docURL($params); } } @@ -1391,7 +1386,7 @@ public static function docURL($params) { * @return mixed */ public static function formatDocUrl($url) { - return preg_replace('#^user/#', 'user/en/stable/', $url); + return preg_replace('#^(user|sysadmin|dev)/#', '\1/en/stable/', $url); } /** @@ -1399,11 +1394,28 @@ public static function formatDocUrl($url) { * * @param int $status * (optional) Code with which to exit. + * + * @param array $testParameters */ - public static function civiExit($status = 0) { + public static function civiExit($status = 0, $testParameters = []) { + + if (CIVICRM_UF === 'UnitTests') { + throw new CRM_Core_Exception_PrematureExitException('civiExit called', $testParameters); + } + if ($status > 0) { + http_response_code(500); + } // move things to CiviCRM cache as needed CRM_Core_Session::storeSessionObjects(); + if (Civi\Core\Container::isContainerBooted()) { + Civi::dispatcher()->dispatch('civi.core.exit'); + } + + $userSystem = CRM_Core_Config::singleton()->userSystem; + if (is_callable([$userSystem, 'onCiviExit'])) { + $userSystem->onCiviExit(); + } exit($status); } @@ -1413,13 +1425,32 @@ public static function civiExit($status = 0) { public static function flushCache() { // flush out all cache entries so we can reload new data // a bit aggressive, but livable for now - $cache = CRM_Utils_Cache::singleton(); - $cache->flush(); + CRM_Utils_Cache::singleton()->flush(); + + // Traditionally, systems running on memory-backed caches were quite + // zealous about destroying *all* memory-backed caches during a flush(). + // These flushes simulate that legacy behavior. However, they should probably + // be removed at some point. + $localDrivers = ['CRM_Utils_Cache_ArrayCache', 'CRM_Utils_Cache_NoCache']; + if (Civi\Core\Container::isContainerBooted() + && !in_array(get_class(CRM_Utils_Cache::singleton()), $localDrivers)) { + Civi::cache('long')->flush(); + Civi::cache('settings')->flush(); + Civi::cache('js_strings')->flush(); + Civi::cache('community_messages')->flush(); + Civi::cache('groups')->flush(); + Civi::cache('navigation')->flush(); + Civi::cache('customData')->flush(); + Civi::cache('contactTypes')->clear(); + Civi::cache('metadata')->clear(); + CRM_Extension_System::singleton()->getCache()->flush(); + CRM_Cxn_CiviCxnHttp::singleton()->getCache()->flush(); + } // also reset the various static memory caches // reset the memory or array cache - CRM_Core_BAO_Cache::deleteGroup('contact fields', NULL, FALSE); + Civi::cache('fields')->flush(); // reset ACL cache CRM_ACL_BAO_Cache::resetCache(); @@ -1446,12 +1477,16 @@ public static function flushCache() { * @param bool $throwError * @param string $realPath */ - public static function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { + public static function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { if (!is_array($params)) { - $params = array(); + $params = []; } $config = CRM_Core_Config::singleton(); - return $config->userSystem->loadBootStrap($params, $loadUser, $throwError, $realPath); + $result = $config->userSystem->loadBootStrap($params, $loadUser, $throwError, $realPath); + if (is_callable([$config->userSystem, 'setMySQLTimeZone'])) { + $config->userSystem->setMySQLTimeZone(); + } + return $result; } /** @@ -1588,8 +1623,7 @@ public static function languageNegotiationURL( $addLanguagePart = TRUE, $removeLanguagePart = FALSE ) { - $config = &CRM_Core_Config::singleton(); - return $config->userSystem->languageNegotiationURL($url, $addLanguagePart, $removeLanguagePart); + return CRM_Core_Config::singleton()->userSystem->languageNegotiationURL($url, $addLanguagePart, $removeLanguagePart); } /** @@ -1642,7 +1676,7 @@ public static function appendTPLFile( * include path. */ public static function listIncludeFiles($relpath) { - $file_list = array(); + $file_list = []; $inc_dirs = explode(PATH_SEPARATOR, get_include_path()); foreach ($inc_dirs as $inc_dir) { $target_dir = $inc_dir . DIRECTORY_SEPARATOR . $relpath; @@ -1680,9 +1714,9 @@ public static function listIncludeFiles($relpath) { * List of plugins, where the plugin name is both the key and the value of * each element. */ - public static function getPluginList($relpath, $fext = '.php', $skipList = array()) { + public static function getPluginList($relpath, $fext = '.php', $skipList = []) { $fext_len = strlen($fext); - $plugins = array(); + $plugins = []; $inc_files = CRM_Utils_System::listIncludeFiles($relpath); foreach ($inc_files as $inc_file) { if (substr($inc_file, 0 - $fext_len) == $fext) { @@ -1724,7 +1758,7 @@ public static function evalUrl($url) { } else { $config = CRM_Core_Config::singleton(); - $vars = array( + $vars = [ '{ver}' => CRM_Utils_System::version(), '{uf}' => $config->userFramework, '{php}' => phpversion(), @@ -1732,7 +1766,7 @@ public static function evalUrl($url) { '{baseUrl}' => $config->userFrameworkBaseURL, '{lang}' => $config->lcMessages, '{co}' => $config->defaultContactCountry, - ); + ]; return strtr($url, array_map('urlencode', $vars)); } } @@ -1749,7 +1783,7 @@ public static function getSiteID() { if (!$sid) { $config = CRM_Core_Config::singleton(); $sid = md5('sid_' . (defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : '') . '_' . $config->userFrameworkBaseURL); - civicrm_api3('Setting', 'create', array('domain_id' => 'all', 'site_id' => $sid)); + civicrm_api3('Setting', 'create', ['domain_id' => 'all', 'site_id' => $sid]); } return $sid; } @@ -1821,9 +1855,9 @@ public static function createDefaultCrudLink($crudLinkSpec) { return NULL; } - $link = array(); + $link = []; CRM_Utils_Hook::crudLink($crudLinkSpec, $bao, $link); - if (empty($link) && is_callable(array($bao, 'createDefaultCrudLink'))) { + if (empty($link) && is_callable([$bao, 'createDefaultCrudLink'])) { $link = $bao->createDefaultCrudLink($crudLinkSpec); } @@ -1837,4 +1871,12 @@ public static function createDefaultCrudLink($crudLinkSpec) { return NULL; } + /** + * Return an HTTP Response with appropriate content and status code set. + * @param \Psr\Http\Message\ResponseInterface $response + */ + public static function sendResponse(\Psr\Http\Message\ResponseInterface $response) { + $config = CRM_Core_Config::singleton()->userSystem->sendResponse($response); + } + } diff --git a/CRM/Utils/System/Backdrop.php b/CRM/Utils/System/Backdrop.php index 88ab5c43863a..e7e8ace39fa3 100644 --- a/CRM/Utils/System/Backdrop.php +++ b/CRM/Utils/System/Backdrop.php @@ -1,9 +1,9 @@ $params['cms_name'], 'mail' => $params[$mail], 'op' => 'Create new account', - ); + ]; $admin = user_access('administer users'); if (!config_get('system.core', 'user_email_verification') || $admin) { - $form_state['input']['pass'] = array('pass1' => $params['cms_pass'], 'pass2' => $params['cms_pass']); + $form_state['input']['pass'] = ['pass1' => $params['cms_pass'], 'pass2' => $params['cms_pass']]; } if (!empty($params['notify'])) { @@ -61,7 +61,7 @@ public function createUser(&$params, $mail) { $form_state['programmed'] = TRUE; $form_state['complete form'] = FALSE; $form_state['method'] = 'post'; - $form_state['build_info']['args'] = array(); + $form_state['build_info']['args'] = []; /* * if we want to submit this form more than once in a process (e.g. create more than one user) * we must force it to validate each time for this form. Otherwise it will not validate @@ -73,12 +73,12 @@ public function createUser(&$params, $mail) { // we also need to redirect b $config->inCiviCRM = TRUE; - $form = drupal_retrieve_form('user_register_form', $form_state); + $form = backdrop_retrieve_form('user_register_form', $form_state); $form_state['process_input'] = 1; $form_state['submitted'] = 1; - $form['#array_parents'] = array(); + $form['#array_parents'] = []; $form['#tree'] = FALSE; - drupal_process_form('user_register_form', $form, $form_state); + backdrop_process_form('user_register_form', $form, $form_state); $config->inCiviCRM = FALSE; @@ -103,7 +103,7 @@ public function updateCMSName($ufID, $email) { } /** - * Check if username and email exists in the drupal db. + * Check if username and email exists in the Backdrop db. * * @param array $params * Array of name and mail values. @@ -115,7 +115,7 @@ public function updateCMSName($ufID, $email) { public static function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') { $errors = form_get_errors(); if ($errors) { - // unset drupal messages to avoid twice display of errors + // unset Backdrop messages to avoid twice display of errors unset($_SESSION['messages']); } @@ -124,23 +124,23 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = $errors['cms_name'] = $nameError; } else { - $uid = db_query("SELECT uid FROM {users} WHERE name = :name", array(':name' => $params['name']))->fetchField(); + $uid = db_query("SELECT uid FROM {users} WHERE name = :name", [':name' => $params['name']])->fetchField(); if ((bool) $uid) { - $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', array(1 => $params['name'])); + $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', [1 => $params['name']]); } } } if (!empty($params['mail'])) { if (!valid_email_address($params['mail'])) { - $errors[$emailName] = ts('The e-mail address %1 is not valid.', array('%1' => $params['mail'])); + $errors[$emailName] = ts('The e-mail address %1 is not valid.', [1 => $params['mail']]); } else { - $uid = db_query("SELECT uid FROM {users} WHERE mail = :mail", array(':mail' => $params['mail']))->fetchField(); + $uid = db_query("SELECT uid FROM {users} WHERE mail = :mail", [':mail' => $params['mail']])->fetchField(); if ((bool) $uid) { $resetUrl = url('user/password'); $errors[$emailName] = ts('The email address %1 already has an account associated with it. Have you forgotten your password?', - array(1 => $params['mail'], 2 => $resetUrl) + [1 => $params['mail'], 2 => $resetUrl] ); } } @@ -151,8 +151,8 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = * @inheritDoc */ public function getLoginURL($destination = '') { - $query = $destination ? array('destination' => $destination) : array(); - return url('user', array('query' => $query, 'absolute' => TRUE)); + $query = $destination ? ['destination' => $destination] : []; + return url('user/login', ['query' => $query, 'absolute' => TRUE]); } /** @@ -164,7 +164,7 @@ public function setTitle($title, $pageTitle = NULL) { $pageTitle = $title; } - drupal_set_title($pageTitle, PASS_THROUGH); + backdrop_set_title($pageTitle, PASS_THROUGH); } } @@ -172,12 +172,12 @@ public function setTitle($title, $pageTitle = NULL) { * @inheritDoc */ public function appendBreadCrumb($breadCrumbs) { - $breadCrumb = drupal_get_breadcrumb(); + $breadCrumb = backdrop_get_breadcrumb(); if (is_array($breadCrumbs)) { foreach ($breadCrumbs as $crumbs) { if (stripos($crumbs['url'], 'id%%')) { - $args = array('cid', 'mid'); + $args = ['cid', 'mid']; foreach ($args as $a) { $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, $_GET @@ -190,15 +190,15 @@ public function appendBreadCrumb($breadCrumbs) { $breadCrumb[] = "{$crumbs['title']}"; } } - drupal_set_breadcrumb($breadCrumb); + backdrop_set_breadcrumb($breadCrumb); } /** * @inheritDoc */ public function resetBreadCrumb() { - $bc = array(); - drupal_set_breadcrumb($bc); + $bc = []; + backdrop_set_breadcrumb($bc); } /** @@ -208,11 +208,11 @@ public function addHTMLHead($header) { static $count = 0; if (!empty($header)) { $key = 'civi_' . ++$count; - $data = array( + $data = [ '#type' => 'markup', '#markup' => $header, - ); - drupal_add_html_head($data, $key); + ]; + backdrop_add_html_head($data, $key); } } @@ -220,7 +220,7 @@ public function addHTMLHead($header) { * @inheritDoc */ public function addScriptUrl($url, $region) { - $params = array('group' => JS_LIBRARY, 'weight' => 10); + $params = ['group' => JS_LIBRARY, 'weight' => 10]; switch ($region) { case 'html-header': case 'page-footer': @@ -230,9 +230,9 @@ public function addScriptUrl($url, $region) { default: return FALSE; } - // If the path is within the drupal directory we can use the more efficient 'file' setting + // If the path is within the Backdrop directory we can use the more efficient 'file' setting $params['type'] = $this->formatResourceUrl($url) ? 'file' : 'external'; - drupal_add_js($url, $params); + backdrop_add_js($url, $params); return TRUE; } @@ -240,7 +240,7 @@ public function addScriptUrl($url, $region) { * @inheritDoc */ public function addScript($code, $region) { - $params = array('type' => 'inline', 'group' => JS_LIBRARY, 'weight' => 10); + $params = ['type' => 'inline', 'group' => JS_LIBRARY, 'weight' => 10]; switch ($region) { case 'html-header': case 'page-footer': @@ -250,7 +250,7 @@ public function addScript($code, $region) { default: return FALSE; } - drupal_add_js($code, $params); + backdrop_add_js($code, $params); return TRUE; } @@ -261,10 +261,10 @@ public function addStyleUrl($url, $region) { if ($region != 'html-header') { return FALSE; } - $params = array(); - // If the path is within the drupal directory we can use the more efficient 'file' setting + $params = []; + // If the path is within the Backdrop directory we can use the more efficient 'file' setting $params['type'] = $this->formatResourceUrl($url) ? 'file' : 'external'; - drupal_add_css($url, $params); + backdrop_add_css($url, $params); return TRUE; } @@ -275,8 +275,8 @@ public function addStyle($code, $region) { if ($region != 'html-header') { return FALSE; } - $params = array('type' => 'inline'); - drupal_add_css($code, $params); + $params = ['type' => 'inline']; + backdrop_add_css($code, $params); return TRUE; } @@ -314,12 +314,12 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP $account = $userUid = $userMail = NULL; if ($loadCMSBootstrap) { - $bootStrapParams = array(); + $bootStrapParams = []; if ($name && $password) { - $bootStrapParams = array( + $bootStrapParams = [ 'name' => $name, 'pass' => $password, - ); + ]; } CRM_Utils_System::loadBootStrap($bootStrapParams, TRUE, TRUE, $realPath); @@ -331,10 +331,12 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP } else { // CRM-8638 - // SOAP cannot load drupal bootstrap and hence we do it the old way + // SOAP cannot load Backdrop bootstrap and hence we do it the old way // Contact CiviSMTP folks if we run into issues with this :) $cmsPath = $this->cmsRootPath(); - + if (!defined('BACKDROP_ROOT')) { + define(BACKDROP_ROOT, $cmsPath); + } require_once "$cmsPath/core/includes/bootstrap.inc"; require_once "$cmsPath/core/includes/password.inc"; @@ -371,7 +373,7 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP if (!$contactID) { return FALSE; } - return array($contactID, $userUid, mt_rand()); + return [$contactID, $userUid, mt_rand()]; } return FALSE; } @@ -406,16 +408,36 @@ public function loadUser($username) { * @param array $params * The array of form values submitted by the user. */ - public function userLoginFinalize($params = array()) { + public function userLoginFinalize($params = []) { user_login_finalize($params); } + /** + * @inheritDoc + */ + public function isUserRegistrationPermitted() { + if (config_get('system.core', 'user_register') == 'admin_only') { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function isPasswordUserGenerated() { + if (config_get('system.core', 'user_email_verification') == TRUE) { + return FALSE; + } + return TRUE; + } + /** * @inheritDoc */ public function getUFLocale() { - // return CiviCRM’s xx_YY locale that either matches Drupal’s Chinese locale - // (for CRM-6281), Drupal’s xx_YY or is retrieved based on Drupal’s xx + // return CiviCRM’s xx_YY locale that either matches Backdrop’s Chinese locale + // (for CRM-6281), Backdrop’s xx_YY or is retrieved based on Backdrop’s xx // sometimes for CLI based on order called, this might not be set and/or empty global $language; @@ -465,7 +487,7 @@ public function setUFLocale($civicrm_language) { * Determine the native ID of the CMS user. * * @param string $username - * @return int|NULL + * @return int|null */ public function getUfId($username) { $user = user_load_by_name($username); @@ -505,7 +527,7 @@ public function getDefaultBlockLocation() { * * @return bool */ - public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { $cmsPath = $this->cmsRootPath($realPath); if (!file_exists("$cmsPath/core/includes/bootstrap.inc")) { @@ -515,11 +537,11 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = } return FALSE; } - // load drupal bootstrap + // load Backdrop bootstrap chdir($cmsPath); define('BACKDROP_ROOT', $cmsPath); - // For drupal multi-site CRM-11313 + // For Backdrop multi-site CRM-11313 if ($realPath && strpos($realPath, 'sites/all/modules/') === FALSE) { preg_match('@sites/([^/]*)/modules@s', $realPath, $matches); if (!empty($matches[1])) { @@ -527,6 +549,7 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = } } require_once "$cmsPath/core/includes/bootstrap.inc"; + require_once "$cmsPath/core/includes/config.inc"; backdrop_bootstrap(BACKDROP_BOOTSTRAP_FULL); // Explicitly setting error reporting, since we cannot handle Backdrop @@ -606,6 +629,11 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = * @inheritDoc */ public function cmsRootPath($scriptFilename = NULL) { + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + $cmsRoot = NULL; $valid = NULL; @@ -666,7 +694,7 @@ public function getLoggedInUfID() { user_is_logged_in() && function_exists('user_uid_optional_to_arg') ) { - $ufID = user_uid_optional_to_arg(array()); + $ufID = user_uid_optional_to_arg([]); } return $ufID; @@ -747,7 +775,7 @@ public function replacePermission($oldPerm, $newPerms) { $roles = user_roles(FALSE, $oldPerm); if (!empty($roles)) { foreach (array_keys($roles) as $rid) { - user_role_revoke_permissions($rid, array($oldPerm)); + user_role_revoke_permissions($rid, [$oldPerm]); user_role_grant_permissions($rid, $newPerms); } } @@ -768,11 +796,11 @@ public function og_membership_create($ogID, $userID) { // @TODO Find more solid way to check - try system_get_info('module', 'og'). // // Also, since we don't know how to get the entity type of the // group, we'll assume it's 'node' - og_group('node', $ogID, array('entity' => user_load($userID))); + og_group('node', $ogID, ['entity' => user_load($userID)]); } else { // Works for the OG 7.x-1.x branch - og_group($ogID, array('entity' => user_load($userID))); + og_group($ogID, ['entity' => user_load($userID)]); } } @@ -852,16 +880,13 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; } /** @@ -882,7 +907,143 @@ public function clearResourceCache() { */ public function permissionEmails($permissionName) { // FIXME!!!! - return array(); + return []; + } + + /** + * @inheritdoc + */ + public function getDefaultFileStorage() { + $config = CRM_Core_Config::singleton(); + $baseURL = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE); + + $siteName = $this->parseBackdropSiteNameFromRequest('/files/civicrm'); + if ($siteName) { + $filesURL = $baseURL . "sites/$siteName/files/civicrm/"; + } + else { + $filesURL = $baseURL . "files/civicrm/"; + } + + return [ + 'url' => $filesURL, + 'path' => CRM_Utils_File::baseFilePath(), + ]; + } + + /** + * Check if a resource url is within the Backdrop directory and format appropriately. + * + * @param $url (reference) + * + * @return bool + * TRUE for internal paths, FALSE for external. The backdrop_add_js fn is able to add js more + * efficiently if it is known to be in the Backdrop site + */ + public function formatResourceUrl(&$url) { + $internal = FALSE; + $base = CRM_Core_Config::singleton()->resourceBase; + global $base_url; + // Handle absolute urls + // compares $url (which is some unknown/untrusted value from a third-party dev) to the CMS's base url (which is independent of civi's url) + // to see if the url is within our Backdrop dir, if it is we are able to treated it as an internal url + if (strpos($url, $base_url) === 0) { + $file = trim(str_replace($base_url, '', $url), '/'); + // CRM-18130: Custom CSS URL not working if aliased or rewritten + if (file_exists(BACKDROP_ROOT . $file)) { + $url = $file; + $internal = TRUE; + } + } + // Handle relative urls that are within the CiviCRM module directory + elseif (strpos($url, $base) === 0) { + $internal = TRUE; + $url = $this->appendCoreDirectoryToResourceBase(dirname(backdrop_get_path('module', 'civicrm')) . '/') . trim(substr($url, strlen($base)), '/'); + } + // Strip query string + $q = strpos($url, '?'); + if ($q && $internal) { + $url = substr($url, 0, $q); + } + return $internal; + } + + /** + * @inheritDoc + */ + public function setMessage($message) { + backdrop_set_message($message); + } + + /** + * @inheritDoc + */ + public function permissionDenied() { + backdrop_access_denied(); + } + + /** + * @inheritDoc + */ + public function flush() { + backdrop_flush_all_caches(); + } + + /** + * Determine if Backdrop multi-site applies to the current request -- and, + * specifically, determine the name of the multisite folder. + * + * @param string $flagFile + * Check if $flagFile exists inside the site dir. + * @return null|string + * string, e.g. `bar.example.com` if using multisite. + * NULL if using the default site. + */ + private function parseBackdropSiteNameFromRequest($flagFile = '') { + $phpSelf = array_key_exists('PHP_SELF', $_SERVER) ? $_SERVER['PHP_SELF'] : ''; + $httpHost = array_key_exists('HTTP_HOST', $_SERVER) ? $_SERVER['HTTP_HOST'] : ''; + if (empty($httpHost)) { + $httpHost = parse_url(CIVICRM_UF_BASEURL, PHP_URL_HOST); + if (parse_url(CIVICRM_UF_BASEURL, PHP_URL_PORT)) { + $httpHost .= ':' . parse_url(CIVICRM_UF_BASEURL, PHP_URL_PORT); + } + } + + $confdir = $this->cmsRootPath() . '/sites'; + + if (file_exists($confdir . "/sites.php")) { + include $confdir . "/sites.php"; + } + else { + $sites = []; + } + + $uri = explode('/', $phpSelf); + $server = explode('.', implode('.', array_reverse(explode(':', rtrim($httpHost, '.'))))); + for ($i = count($uri) - 1; $i > 0; $i--) { + for ($j = count($server); $j > 0; $j--) { + $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); + if (file_exists("$confdir/$dir" . $flagFile)) { + \Civi::$statics[__CLASS__]['drupalSiteName'] = $dir; + return \Civi::$statics[__CLASS__]['drupalSiteName']; + } + // check for alias + if (isset($sites[$dir]) && file_exists("$confdir/{$sites[$dir]}" . $flagFile)) { + \Civi::$statics[__CLASS__]['drupalSiteName'] = $sites[$dir]; + return \Civi::$statics[__CLASS__]['drupalSiteName']; + } + } + } + } + + /** + * Append Backdrop CSS and JS to coreResourcesList. + * + * @param \Civi\Core\Event\GenericHookEvent $e + */ + public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) { + $e->list[] = 'css/backdrop.css'; + $e->list[] = 'js/crm.backdrop.js'; } } diff --git a/CRM/Utils/System/Base.php b/CRM/Utils/System/Base.php index a66fba129e94..0dca55f65783 100644 --- a/CRM/Utils/System/Base.php +++ b/CRM/Utils/System/Base.php @@ -11,44 +11,44 @@ abstract class CRM_Utils_System_Base { * The correct method is to have functions on the UF classes for all UF specific * functions and leave the codebase oblivious to the type of CMS * - * @deprecated * @var bool + * @deprecated * TRUE, if the CMS is Drupal. */ - var $is_drupal = FALSE; + public $is_drupal = FALSE; /** * Deprecated property to check if this is a joomla install. The correct method is to have functions on the UF classes for all UF specific * functions and leave the codebase oblivious to the type of CMS * - * @deprecated * @var bool + * @deprecated * TRUE, if the CMS is Joomla!. */ - var $is_joomla = FALSE; + public $is_joomla = FALSE; /** * deprecated property to check if this is a wordpress install. The correct method is to have functions on the UF classes for all UF specific * functions and leave the codebase oblivious to the type of CMS * - * @deprecated * @var bool + * @deprecated * TRUE, if the CMS is WordPress. */ - var $is_wordpress = FALSE; + public $is_wordpress = FALSE; /** * Does this CMS / UF support a CMS specific logging mechanism? - * @todo - we should think about offering up logging mechanisms in a way that is also extensible by extensions * @var bool + * @todo - we should think about offering up logging mechanisms in a way that is also extensible by extensions */ - var $supports_UF_Logging = FALSE; + public $supports_UF_Logging = FALSE; /** * @var bool * TRUE, if the CMS allows CMS forms to be extended by hooks. */ - var $supports_form_extensions = FALSE; + public $supports_form_extensions = FALSE; public function initialize() { if (\CRM_Utils_System::isSSL()) { @@ -56,6 +56,8 @@ public function initialize() { } } + abstract public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL); + /** * Append an additional breadcrumb tag to the existing breadcrumb. * @@ -244,7 +246,7 @@ function_exists('theme') && if ($region = CRM_Core_Region::instance('html-header', FALSE)) { CRM_Utils_System::addHTMLHead($region->render('')); } - print theme('maintenance_page', array('content' => $content)); + print theme('maintenance_page', ['content' => $content]); exit(); } // TODO: Figure out why D7 returns but everyone else prints @@ -252,10 +254,9 @@ function_exists('theme') && } $out = $content; - $config = &CRM_Core_Config::singleton(); if ( !$print && - $config->userFramework == 'WordPress' + CRM_Core_Config::singleton()->userFramework == 'WordPress' ) { if (!function_exists('is_admin')) { throw new \Exception('Function "is_admin()" is missing, even though WordPress is the user framework.'); @@ -395,6 +396,37 @@ public function isUserLoggedIn() { return FALSE; } + /** + * Check if user registration is permitted. + * + * @return bool + */ + public function isUserRegistrationPermitted() { + return FALSE; + } + + /** + * Check if user can create passwords or is initially assigned a system-generated one. + * + * @return bool + */ + public function isPasswordUserGenerated() { + return FALSE; + } + + /** + * Is a front end page being accessed. + * + * Generally this would be a contribution form or other public page as opposed to a backoffice page (like contact edit). + * + * @todo Drupal uses the is_public setting - clarify & rationalise. See https://github.com/civicrm/civicrm-drupal/pull/546/files + * + * @return bool + */ + public function isFrontEndPage() { + return CRM_Core_Config::singleton()->userFrameworkFrontend; + } + /** * Get user login URL for hosting CMS (method declared in each CMS system class) * @@ -404,7 +436,7 @@ public function isUserLoggedIn() { * @return string * loginURL for the current CMS */ - public abstract function getLoginURL($destination = ''); + abstract public function getLoginURL($destination = ''); /** * Get the login destination string. @@ -563,7 +595,7 @@ public function setTitle($title, $pageTitle = NULL) { public function getDefaultSiteSettings($dir) { $config = CRM_Core_Config::singleton(); $url = $config->userFrameworkBaseURL; - return array($url, NULL, NULL); + return [$url, NULL, NULL]; } /** @@ -593,15 +625,6 @@ public function getDefaultFileStorage() { $tempURL = str_replace("/administrator/", "/", $baseURL); $filesURL = $tempURL . "media/civicrm/"; } - elseif ($this->is_drupal) { - $siteName = $config->userSystem->parseDrupalSiteName($civicrm_root); - if ($siteName) { - $filesURL = $baseURL . "sites/$siteName/files/civicrm/"; - } - else { - $filesURL = $baseURL . "sites/default/files/civicrm/"; - } - } elseif ($config->userFramework == 'UnitTests') { $filesURL = $baseURL . "sites/default/files/civicrm/"; } @@ -609,10 +632,10 @@ public function getDefaultFileStorage() { throw new CRM_Core_Exception("Failed to locate default file storage ($config->userFramework)"); } - return array( + return [ 'url' => $filesURL, 'path' => CRM_Utils_File::baseFilePath(), - ); + ]; } /** @@ -659,7 +682,7 @@ public function getCiviSourceStorage() { str_replace('\\', '/', $civicrm_root) ); - $siteName = $config->userSystem->parseDrupalSiteName($civicrm_root); + $siteName = $config->userSystem->parseDrupalSiteNameFromRoot($civicrm_root); if ($siteName) { $civicrmDirName = trim(basename($civicrm_root)); $userFrameworkResourceURL = $baseURL . "sites/$siteName/modules/$civicrmDirName/"; @@ -669,10 +692,10 @@ public function getCiviSourceStorage() { $userFrameworkResourceURL = NULL; } - return array( - 'url' => CRM_Utils_File::addTrailingSlash($userFrameworkResourceURL), + return [ + 'url' => CRM_Utils_File::addTrailingSlash($userFrameworkResourceURL, '/'), 'path' => CRM_Utils_File::addTrailingSlash($civicrm_root), - ); + ]; } /** @@ -685,7 +708,7 @@ public function getCiviSourceStorage() { * * FIXME: Document values accepted/required by $params */ - public function userLoginFinalize($params = array()) { + public function userLoginFinalize($params = []) { } /** @@ -699,7 +722,6 @@ public function setMySQLTimeZone() { } } - /** * Get timezone from CMS. * @@ -716,6 +738,11 @@ public function getTimeZoneOffset() { $dateTime = new DateTime("now", $tzObj); $tz = $tzObj->getOffset($dateTime); + if ($tz === 0) { + // CRM-21422 + return '+00:00'; + } + if (empty($tz)) { return FALSE; } @@ -775,14 +802,14 @@ public function getUserIDFromUserObject($user) { * - name (ie the system user name. */ public function getUser($contactID) { - $ufMatch = civicrm_api3('UFMatch', 'getsingle', array( + $ufMatch = civicrm_api3('UFMatch', 'getsingle', [ 'contact_id' => $contactID, 'domain_id' => CRM_Core_Config::domainID(), - )); - return array( + ]); + return [ 'id' => $ufMatch['uf_id'], 'name' => $ufMatch['uf_name'], - ); + ]; } /** @@ -855,7 +882,7 @@ public function getBestUFUniqueIdentifier($user = NULL) { * [CRM_Core_Module] */ public function getModules() { - return array(); + return []; } /** @@ -899,9 +926,17 @@ public function logger($message) { /** * Append to coreResourcesList. * - * @param array $list + * @param \Civi\Core\Event\GenericHookEvent $e + */ + public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) { + } + + /** + * Modify dynamic assets. + * + * @param \Civi\Core\Event\GenericHookEvent $e */ - public function appendCoreResources(&$list) { + public function alterAssetUrl(\Civi\Core\Event\GenericHookEvent $e) { } /** @@ -920,7 +955,21 @@ public function setHttpHeader($name, $value) { */ public function synchronizeUsers() { throw new Exception('CMS user creation not supported for this framework'); - return array(); + return []; + } + + /** + * Send an HTTP Response base on PSR HTTP RespnseInterface response. + * + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function sendResponse(\Psr\Http\Message\ResponseInterface $response) { + http_response_code($response->getStatusCode()); + foreach ($response->getHeaders() as $name => $values) { + CRM_Utils_System::setHttpHeader($name, implode(', ', (array) $values)); + } + echo $response->getBody(); + CRM_Utils_System::civiExit(); } } diff --git a/CRM/Utils/System/Drupal.php b/CRM/Utils/System/Drupal.php index 1c9d8b288057..44e4349b8687 100644 --- a/CRM/Utils/System/Drupal.php +++ b/CRM/Utils/System/Drupal.php @@ -1,9 +1,9 @@ $params['cms_name'], 'mail' => $params[$mail], 'op' => 'Create new account', - ); + ]; $admin = user_access('administer users'); if (!variable_get('user_email_verification', TRUE) || $admin) { - $form_state['input']['pass'] = array('pass1' => $params['cms_pass'], 'pass2' => $params['cms_pass']); + $form_state['input']['pass'] = ['pass1' => $params['cms_pass'], 'pass2' => $params['cms_pass']]; } if (!empty($params['notify'])) { @@ -61,7 +61,7 @@ public function createUser(&$params, $mail) { $form_state['programmed'] = TRUE; $form_state['complete form'] = FALSE; $form_state['method'] = 'post'; - $form_state['build_info']['args'] = array(); + $form_state['build_info']['args'] = []; /* * if we want to submit this form more than once in a process (e.g. create more than one user) * we must force it to validate each time for this form. Otherwise it will not validate @@ -76,7 +76,7 @@ public function createUser(&$params, $mail) { $form = drupal_retrieve_form('user_register_form', $form_state); $form_state['process_input'] = 1; $form_state['submitted'] = 1; - $form['#array_parents'] = array(); + $form['#array_parents'] = []; $form['#tree'] = FALSE; drupal_process_form('user_register_form', $form, $form_state); @@ -96,7 +96,7 @@ public function updateCMSName($ufID, $ufName) { if (function_exists('user_load')) { $user = user_load($ufID); if ($user->mail != $ufName) { - user_save($user, array('mail' => $ufName)); + user_save($user, ['mail' => $ufName]); $user = user_load($ufID); } } @@ -131,10 +131,10 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = else { $uid = db_query( "SELECT uid FROM {users} WHERE name = :name", - array(':name' => $params['name']) + [':name' => $params['name']] )->fetchField(); if ((bool) $uid) { - $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', array(1 => $params['name'])); + $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', [1 => $params['name']]); } } } @@ -146,12 +146,12 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = else { $uid = db_query( "SELECT uid FROM {users} WHERE mail = :mail", - array(':mail' => $params['mail']) + [':mail' => $params['mail']] )->fetchField(); if ((bool) $uid) { $resetUrl = url('user/password'); $errors[$emailName] = ts('The email address %1 already has an account associated with it. Have you forgotten your password?', - array(1 => $params['mail'], 2 => $resetUrl) + [1 => $params['mail'], 2 => $resetUrl] ); } } @@ -162,7 +162,7 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = * @inheritDoc */ public function getLoginURL($destination = '') { - $query = $destination ? array('destination' => $destination) : NULL; + $query = $destination ? ['destination' => $destination] : NULL; return CRM_Utils_System::url('user', $query, TRUE); } @@ -188,7 +188,7 @@ public function appendBreadCrumb($breadCrumbs) { if (is_array($breadCrumbs)) { foreach ($breadCrumbs as $crumbs) { if (stripos($crumbs['url'], 'id%%')) { - $args = array('cid', 'mid'); + $args = ['cid', 'mid']; foreach ($args as $a) { $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, $_GET @@ -208,7 +208,7 @@ public function appendBreadCrumb($breadCrumbs) { * @inheritDoc */ public function resetBreadCrumb() { - $bc = array(); + $bc = []; drupal_set_breadcrumb($bc); } @@ -219,10 +219,10 @@ public function addHTMLHead($header) { static $count = 0; if (!empty($header)) { $key = 'civi_' . ++$count; - $data = array( + $data = [ '#type' => 'markup', '#markup' => $header, - ); + ]; drupal_add_html_head($data, $key); } } @@ -231,7 +231,7 @@ public function addHTMLHead($header) { * @inheritDoc */ public function addScriptUrl($url, $region) { - $params = array('group' => JS_LIBRARY, 'weight' => 10); + $params = ['group' => JS_LIBRARY, 'weight' => 10]; switch ($region) { case 'html-header': case 'page-footer': @@ -251,7 +251,7 @@ public function addScriptUrl($url, $region) { * @inheritDoc */ public function addScript($code, $region) { - $params = array('type' => 'inline', 'group' => JS_LIBRARY, 'weight' => 10); + $params = ['type' => 'inline', 'group' => JS_LIBRARY, 'weight' => 10]; switch ($region) { case 'html-header': case 'page-footer': @@ -272,7 +272,7 @@ public function addStyleUrl($url, $region) { if ($region != 'html-header') { return FALSE; } - $params = array(); + $params = []; // If the path is within the drupal directory we can use the more efficient 'file' setting $params['type'] = $this->formatResourceUrl($url) ? 'file' : 'external'; drupal_add_css($url, $params); @@ -286,7 +286,7 @@ public function addStyle($code, $region) { if ($region != 'html-header') { return FALSE; } - $params = array('type' => 'inline'); + $params = ['type' => 'inline']; drupal_add_css($code, $params); return TRUE; } @@ -327,12 +327,12 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP $account = $userUid = $userMail = NULL; if ($loadCMSBootstrap) { - $bootStrapParams = array(); + $bootStrapParams = []; if ($name && $password) { - $bootStrapParams = array( + $bootStrapParams = [ 'name' => $name, 'pass' => $password, - ); + ]; } CRM_Utils_System::loadBootStrap($bootStrapParams, TRUE, TRUE, $realPath); @@ -384,7 +384,7 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP if (!$contactID) { return FALSE; } - return array($contactID, $userUid, mt_rand()); + return [$contactID, $userUid, mt_rand()]; } return FALSE; } @@ -420,7 +420,7 @@ public function loadUser($username) { * * FIXME: Document values accepted/required by $params */ - public function userLoginFinalize($params = array()) { + public function userLoginFinalize($params = []) { user_login_finalize($params); } @@ -428,7 +428,7 @@ public function userLoginFinalize($params = array()) { * Determine the native ID of the CMS user. * * @param string $username - * @return int|NULL + * @return int|null */ public function getUfId($username) { $user = user_load_by_name($username); @@ -468,7 +468,7 @@ public function getDefaultBlockLocation() { * * @return bool */ - public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { //take the cms root path. $cmsPath = $this->cmsRootPath($realPath); @@ -590,6 +590,12 @@ public function cmsRootPath($scriptFilename = NULL) { // drush anyway takes care of multisite install etc return drush_get_context('DRUSH_DRUPAL_ROOT'); } + + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + // CRM-7582 $pathVars = explode('/', str_replace('//', '/', @@ -601,7 +607,7 @@ public function cmsRootPath($scriptFilename = NULL) { //need to get back for windows. $firstVar = array_shift($pathVars); - //lets remove sript name to reduce one iteration. + // Remove the script name to remove an necessary iteration of the loop. array_pop($pathVars); // CRM-7429 -- do check for uppermost 'includes' dir, which would @@ -642,7 +648,7 @@ public function getLoggedInUfID() { user_is_logged_in() && function_exists('user_uid_optional_to_arg') ) { - $ufID = user_uid_optional_to_arg(array()); + $ufID = user_uid_optional_to_arg([]); } return $ufID; @@ -725,7 +731,7 @@ public function replacePermission($oldPerm, $newPerms) { $roles = user_roles(FALSE, $oldPerm); if (!empty($roles)) { foreach (array_keys($roles) as $rid) { - user_role_revoke_permissions($rid, array($oldPerm)); + user_role_revoke_permissions($rid, [$oldPerm]); user_role_grant_permissions($rid, $newPerms); } } @@ -746,11 +752,11 @@ public function og_membership_create($ogID, $drupalID) { // @TODO Find more solid way to check - try system_get_info('module', 'og'). // // Also, since we don't know how to get the entity type of the // group, we'll assume it's 'node' - og_group('node', $ogID, array('entity' => user_load($drupalID))); + og_group('node', $ogID, ['entity' => user_load($drupalID)]); } else { // Works for the OG 7.x-1.x branch - og_group($ogID, array('entity' => user_load($drupalID))); + og_group($ogID, ['entity' => user_load($drupalID)]); } } @@ -830,16 +836,26 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; + } + + /** + * Commit the session before exiting. + * Similar to drupal_exit(). + */ + public function onCiviExit() { + if (function_exists('module_invoke_all')) { + if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { + module_invoke_all('exit'); + } + drupal_session_commit(); + } } } diff --git a/CRM/Utils/System/Drupal6.php b/CRM/Utils/System/Drupal6.php index 54a3d0c007ed..af566e5d2a78 100644 --- a/CRM/Utils/System/Drupal6.php +++ b/CRM/Utils/System/Drupal6.php @@ -1,9 +1,9 @@ $params['cms_name'], 'mail' => $params[$mail], 'op' => 'Create new account', - ); + ]; $admin = user_access('administer users'); if (!variable_get('user_email_verification', TRUE) || $admin) { @@ -122,10 +122,10 @@ public function createUser(&$params, $mail) { public function updateCMSName($ufID, $ufName) { // CRM-5555 if (function_exists('user_load')) { - $user = user_load(array('uid' => $ufID)); + $user = user_load(['uid' => $ufID]); if ($user->mail != $ufName) { - user_save($user, array('mail' => $ufName)); - $user = user_load(array('uid' => $ufID)); + user_save($user, ['mail' => $ufName]); + $user = user_load(['uid' => $ufID]); } } } @@ -185,18 +185,18 @@ public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email $dbEmail = CRM_Utils_Array::value('mail', $row); if (strtolower($dbName) == strtolower($name)) { $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', - array(1 => $name) + [1 => $name] ); } if (strtolower($dbEmail) == strtolower($email)) { if (empty($email)) { $errors[$emailName] = ts('You cannot create an email account for a contact with no email', - array(1 => $email) + [1 => $email] ); } else { $errors[$emailName] = ts('This email %1 already has an account associated with it. Please select another email.', - array(1 => $email) + [1 => $email] ); } } @@ -225,7 +225,7 @@ public function appendBreadCrumb($breadCrumbs) { if (is_array($breadCrumbs)) { foreach ($breadCrumbs as $crumbs) { if (stripos($crumbs['url'], 'id%%')) { - $args = array('cid', 'mid'); + $args = ['cid', 'mid']; foreach ($args as $a) { $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, $_GET @@ -245,7 +245,7 @@ public function appendBreadCrumb($breadCrumbs) { * @inheritDoc */ public function resetBreadCrumb() { - $bc = array(); + $bc = []; drupal_set_breadcrumb($bc); } @@ -339,16 +339,16 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP else { //success if ($loadCMSBootstrap) { - $bootStrapParams = array(); + $bootStrapParams = []; if ($name && $password) { - $bootStrapParams = array( + $bootStrapParams = [ 'name' => $name, 'pass' => $password, - ); + ]; } CRM_Utils_System::loadBootStrap($bootStrapParams, TRUE, TRUE, $realPath); } - return array($contactID, $row['uid'], mt_rand()); + return [$contactID, $row['uid'], mt_rand()]; } } return FALSE; @@ -359,7 +359,7 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP */ public function loadUser($username) { global $user; - $user = user_load(array('name' => $username)); + $user = user_load(['name' => $username]); if (empty($user->uid)) { return FALSE; } @@ -383,7 +383,7 @@ public function loadUser($username) { * * FIXME: Document values accepted/required by $params */ - public function userLoginFinalize($params = array()) { + public function userLoginFinalize($params = []) { user_authenticate_finalize($params); } @@ -391,10 +391,10 @@ public function userLoginFinalize($params = array()) { * Determine the native ID of the CMS user. * * @param string $username - * @return int|NULL + * @return int|null */ public function getUfId($username) { - $user = user_load(array('name' => $username)); + $user = user_load(['name' => $username]); if (empty($user->uid)) { return NULL; } @@ -422,7 +422,7 @@ public function logout() { * * @return bool */ - public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { //take the cms root path. $cmsPath = $this->cmsRootPath($realPath); @@ -483,7 +483,7 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = $pass = CRM_Utils_Array::value('pass', $params, FALSE) ? $params['pass'] : trim(CRM_Utils_Array::value('pass', $_REQUEST)); if ($name) { - $user = user_authenticate(array('name' => $name, 'pass' => $pass)); + $user = user_authenticate(['name' => $name, 'pass' => $pass]); if (!$user->uid) { if ($throwError) { echo '
    Sorry, unrecognized username or password.'; @@ -543,6 +543,12 @@ public function cmsRootPath($scriptFilename = NULL) { // drush anyway takes care of multisite install etc return drush_get_context('DRUSH_DRUPAL_ROOT'); } + + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + // CRM-7582 $pathVars = explode('/', str_replace('//', '/', @@ -595,7 +601,7 @@ public function getLoggedInUfID() { user_is_logged_in() && function_exists('user_uid_optional_to_arg') ) { - $ufID = user_uid_optional_to_arg(array()); + $ufID = user_uid_optional_to_arg([]); } return $ufID; @@ -622,10 +628,10 @@ public function languageNegotiationURL($url, $addLanguagePart = TRUE, $removeLan //url prefix / path. if (isset($language->prefix) && $language->prefix && - in_array($mode, array( + in_array($mode, [ LANGUAGE_NEGOTIATION_PATH, LANGUAGE_NEGOTIATION_PATH_DEFAULT, - )) + ]) ) { if ($addLanguagePart) { @@ -694,7 +700,7 @@ public function replacePermission($oldPerm, $newPerms) { * @inheritDoc */ public function getModules() { - $result = array(); + $result = []; $q = db_query('SELECT name, status FROM {system} WHERE type = \'module\' AND schema_version <> -1'); while ($row = db_fetch_object($q)) { $result[] = new CRM_Core_Module('drupal.' . $row->name, ($row->status == 1) ? TRUE : FALSE); @@ -725,7 +731,7 @@ public function getLoginURL($destination = '') { * Drupal User ID. */ public function og_membership_create($ogID, $drupalID) { - og_save_subscription($ogID, $drupalID, array('is_active' => 1)); + og_save_subscription($ogID, $drupalID, ['is_active' => 1]); } /** @@ -752,9 +758,18 @@ public function getTimeZoneString() { else { $timezone = variable_get('date_default_timezone', NULL); } + + // Retrieved timezone will be represented as GMT offset in seconds but, according + // to the doc for the overridden method, ought to be returned as a region string + // (e.g., America/Havana). + if (strlen($timezone)) { + $timezone = timezone_name_from_abbr("", (int) $timezone); + } + if (!$timezone) { $timezone = parent::getTimeZoneString(); } + return $timezone; } @@ -773,7 +788,7 @@ public function synchronizeUsers() { if (PHP_SAPI != 'cli') { set_time_limit(300); } - $rows = array(); + $rows = []; $id = 'uid'; $mail = 'mail'; $name = 'name'; @@ -800,16 +815,13 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; } } diff --git a/CRM/Utils/System/Drupal8.php b/CRM/Utils/System/Drupal8.php index 8d931aa04560..38ed2933c671 100644 --- a/CRM/Utils/System/Drupal8.php +++ b/CRM/Utils/System/Drupal8.php @@ -1,9 +1,9 @@ setUsername($params['cms_name'])->setEmail($params[$mail]); @@ -64,6 +65,9 @@ public function createUser(&$params, $mail) { if ($user_register_conf != 'visitors' && !$user->hasPermission('administer users')) { $account->block(); } + elseif (!$verify_mail_conf) { + $account->activate(); + } // Validate the user object $violations = $account->validate(); @@ -71,10 +75,16 @@ public function createUser(&$params, $mail) { return FALSE; } + // Let the Drupal module know we're already in CiviCRM. + $config = CRM_Core_Config::singleton(); + $config->inCiviCRM = TRUE; + try { $account->save(); + $config->inCiviCRM = FALSE; } catch (\Drupal\Core\Entity\EntityStorageException $e) { + $config->inCiviCRM = FALSE; return FALSE; } @@ -100,6 +110,11 @@ public function createUser(&$params, $mail) { break; } + // If this is a user creating their own account, login them in! + if ($account->isActive() && $user->isAnonymous()) { + \user_login_finalize($account); + } + return $account->id(); } @@ -139,10 +154,10 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = $violations = iterator_to_array($user->validate()); // We only care about violations on the username field; discard the rest. $violations = array_filter($violations, function ($v) { - return $v->getPropertyPath() == 'name.0.value'; + return $v->getPropertyPath() == 'name'; }); if (count($violations) > 0) { - $errors['cms_name'] = $violations[0]->getMessage(); + $errors['cms_name'] = (string) $violations[0]->getMessage(); } } @@ -157,10 +172,10 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = $violations = iterator_to_array($user->validate()); // We only care about violations on the email field; discard the rest. $violations = array_filter($violations, function ($v) { - return $v->getPropertyPath() == 'mail.0.value'; + return $v->getPropertyPath() == 'mail'; }); if (count($violations) > 0) { - $errors[$emailName] = $violations[0]->getMessage(); + $errors[$emailName] = (string) $violations[0]->getMessage(); } } } @@ -169,8 +184,8 @@ public static function checkUserNameEmailExists(&$params, &$errors, $emailName = * @inheritDoc */ public function getLoginURL($destination = '') { - $query = $destination ? array('destination' => $destination) : array(); - return \Drupal::url('user.page', array(), array('query' => $query)); + $query = $destination ? ['destination' => $destination] : []; + return \Drupal::url('user.login', [], ['query' => $query]); } /** @@ -207,54 +222,6 @@ public function addHTMLHead($header) { \Drupal::service('civicrm.page_state')->addHtmlHeader($header); } - /** - * @inheritDoc - */ - public function addScriptUrl($url, $region) { - static $weight = 0; - - switch ($region) { - case 'html-header': - case 'page-footer': - break; - - default: - return FALSE; - } - - $script = array( - '#tag' => 'script', - '#attributes' => array( - 'src' => $url, - ), - '#weight' => $weight, - ); - $weight++; - \Drupal::service('civicrm.page_state')->addJS($script); - return TRUE; - } - - /** - * @inheritDoc - */ - public function addScript($code, $region) { - switch ($region) { - case 'html-header': - case 'page-footer': - break; - - default: - return FALSE; - } - - $script = array( - '#tag' => 'script', - '#value' => $code, - ); - \Drupal::service('civicrm.page_state')->addJS($script); - return TRUE; - } - /** * @inheritDoc */ @@ -262,13 +229,13 @@ public function addStyleUrl($url, $region) { if ($region != 'html-header') { return FALSE; } - $css = array( + $css = [ '#tag' => 'link', - '#attributes' => array( + '#attributes' => [ 'href' => $url, 'rel' => 'stylesheet', - ), - ); + ], + ]; \Drupal::service('civicrm.page_state')->addCSS($css); return TRUE; } @@ -280,10 +247,10 @@ public function addStyle($code, $region) { if ($region != 'html-header') { return FALSE; } - $css = array( + $css = [ '#tag' => 'style', '#value' => $code, - ); + ]; \Drupal::service('civicrm.page_state')->addCSS($css); return TRUE; } @@ -333,27 +300,21 @@ public function url( ) { $query = html_entity_decode($query); - $url = \Drupal\civicrm\CivicrmHelper::parseURL("{$path}?{$query}"); + $config = CRM_Core_Config::singleton(); + $base = $absolute ? $config->userFrameworkBaseURL : 'internal:/'; + + $url = $this->parseURL("{$path}?{$query}"); // Not all links that CiviCRM generates are Drupal routes, so we use the weaker ::fromUri method. try { - $url = \Drupal\Core\Url::fromUri("base:{$url['path']}", array( + $url = \Drupal\Core\Url::fromUri("{$base}{$url['path']}", array( 'query' => $url['query'], 'fragment' => $fragment, 'absolute' => $absolute, ))->toString(); } catch (Exception $e) { - // @Todo: log to watchdog - $url = ''; - } - - // Special case: CiviCRM passes us "*path*?*query*" as a skeleton, but asterisks - // are invalid and Drupal will attempt to escape them. We unescape them here: - if ($path == '*path*') { - // First remove trailing equals sign that has been added since the key '?*query*' has no value. - $url = rtrim($url, '='); - $url = urldecode($url); + \Drupal::logger('civicrm')->error($e->getMessage()); } return $url; @@ -364,12 +325,17 @@ public function url( */ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) { $system = new CRM_Utils_System_Drupal8(); - $system->loadBootStrap(array(), FALSE); + $system->loadBootStrap([], FALSE); $uid = \Drupal::service('user.auth')->authenticate($name, $password); - $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid); + if ($uid) { + if ($this->loadUser($name)) { + $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid); + return [$contact_id, $uid, mt_rand()]; + } + } - return array($contact_id, $uid, mt_rand()); + return FALSE; } /** @@ -398,7 +364,7 @@ public function loadUser($username) { * Determine the native ID of the CMS user. * * @param string $username - * @return int|NULL + * @return int|null */ public function getUfId($username) { if ($id = user_load_by_name($username)->id()) { @@ -435,7 +401,7 @@ public function logout() { * @return bool * @Todo Handle setting cleanurls configuration for CiviCRM? */ - public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { static $run_once = FALSE; if ($run_once) { return TRUE; @@ -450,25 +416,28 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = chdir($root); // Create a mock $request object - $autoloader = require_once $root . '/vendor/autoload.php'; + $autoloader = require_once $root . '/autoload.php'; + if ($autoloader === TRUE) { + $autoloader = ComposerAutoloaderInitDrupal8::getLoader(); + } // @Todo: do we need to handle case where $_SERVER has no HTTP_HOST key, ie. when run via cli? - $request = new \Symfony\Component\HttpFoundation\Request(array(), array(), array(), array(), array(), $_SERVER); + $request = new \Symfony\Component\HttpFoundation\Request([], [], [], [], [], $_SERVER); // Create a kernel and boot it. \Drupal\Core\DrupalKernel::createFromRequest($request, $autoloader, 'prod')->prepareLegacyRequest($request); // Initialize Civicrm - \Drupal::service('civicrm'); + \Drupal::service('civicrm')->initialize(); // We need to call the config hook again, since we now know // all the modules that are listening on it (CRM-8655). CRM_Utils_Hook::config($config); if ($loadUser) { - if (!empty($params['uid']) && $username = \Drupal\user\Entity\User::load($uid)->getUsername()) { + if (!empty($params['uid']) && $username = \Drupal\user\Entity\User::load($params['uid'])->getUsername()) { $this->loadUser($username); } - elseif (!empty($params['name']) && !empty($params['pass']) && $this->authenticate($params['name'], $params['pass'])) { + elseif (!empty($params['name']) && !empty($params['pass']) && \Drupal::service('user.auth')->authenticate($params['name'], $params['pass'])) { $this->loadUser($params['name']); } } @@ -483,6 +452,11 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = * @return NULL|string */ public function cmsRootPath($path = NULL) { + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + if (defined('DRUPAL_ROOT')) { return DRUPAL_ROOT; } @@ -518,6 +492,33 @@ public function isUserLoggedIn() { return \Drupal::currentUser()->isAuthenticated(); } + /** + * @inheritDoc + */ + public function isUserRegistrationPermitted() { + if (\Drupal::config('user.settings')->get('register') == 'admin_only') { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function isPasswordUserGenerated() { + if (\Drupal::config('user.settings')->get('verify_mail') == TRUE) { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function updateCategories() { + // @todo Is anything necessary? + } + /** * @inheritDoc */ @@ -550,7 +551,7 @@ public function flush() { * @inheritDoc */ public function getModules() { - $modules = array(); + $modules = []; $module_data = system_rebuild_module_data(); foreach ($module_data as $module_name => $extension) { @@ -562,6 +563,16 @@ public function getModules() { return $modules; } + /** + * @inheritDoc + */ + public function getUser($contactID) { + $user_details = parent::getUser($contactID); + $user_details['name'] = $user_details['name']->value; + $user_details['email'] = $user_details['email']->value; + return $user_details; + } + /** * @inheritDoc */ @@ -585,7 +596,7 @@ public function synchronizeUsers() { set_time_limit(300); } - $users = array(); + $users = []; $users = \Drupal::entityTypeManager()->getStorage('user')->loadByProperties(); $uf = $config->userFramework; @@ -605,16 +616,23 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; + } + + /** + * @inheritDoc + */ + public function setMessage($message) { + // CiviCRM sometimes includes markup in messages (ex: Event Cart) + // it needs to be rendered before being displayed. + $message = \Drupal\Core\Render\Markup::create($message); + \Drupal::messenger()->addMessage($message); } /** @@ -633,4 +651,155 @@ public function postURL($action) { return $this->url($current_path); } + /** + * Function to return current language of Drupal8 + * + * @return string + */ + public function getCurrentLanguage() { + // Drupal might not be bootstrapped if being called by the REST API. + if (!class_exists('Drupal') || !\Drupal::hasContainer()) { + return NULL; + } + + return \Drupal::languageManager()->getCurrentLanguage()->getId(); + } + + /** + * Helper function to extract path, query and route name from Civicrm URLs. + * + * For example, 'civicrm/contact/view?reset=1&cid=66' will be returned as: + * + * @code + * array( + * 'path' => 'civicrm/contact/view', + * 'route' => 'civicrm.civicrm_contact_view', + * 'query' => array('reset' => '1', 'cid' => '66'), + * ); + * @endcode + * + * @param string $url + * The url to parse. + * + * @return string[] + * The parsed url parts, containing 'path', 'route' and 'query'. + */ + public function parseUrl($url) { + $processed = ['path' => '', 'route_name' => '', 'query' => []]; + + // Remove leading '/' if it exists. + $url = ltrim($url, '/'); + + // Separate out the url into its path and query components. + $url = parse_url($url); + if (empty($url['path'])) { + return $processed; + } + $processed['path'] = $url['path']; + + // Create a route name by replacing the forward slashes in the path with + // underscores, civicrm/contact/search => civicrm.civicrm_contact_search. + $processed['route_name'] = 'civicrm.' . implode('_', explode('/', $url['path'])); + + // Turn the query string (if it exists) into an associative array. + if (!empty($url['query'])) { + parse_str($url['query'], $processed['query']); + } + + return $processed; + } + + /** + * Append Drupal8 js to coreResourcesList. + * + * @param \Civi\Core\Event\GenericHookEvent $e + */ + public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) { + $e->list[] = 'js/crm.drupal8.js'; + } + + /** + * @inheritDoc + */ + public function setUFLocale($civicrm_language) { + $langcode = substr(str_replace('_', '', $civicrm_language), 0, 2); + $languageManager = \Drupal::languageManager(); + $languages = $languageManager->getLanguages(); + + if (isset($languages[$langcode])) { + $languageManager->setConfigOverrideLanguage($languages[$langcode]); + + // Config must be re-initialized to reset the base URL + // otherwise links will have the wrong language prefix/domain. + $config = CRM_Core_Config::singleton(); + $config->free(); + + return TRUE; + } + + return FALSE; + } + + /** + * @inheritDoc + */ + public function languageNegotiationURL($url, $addLanguagePart = TRUE, $removeLanguagePart = FALSE) { + if (empty($url)) { + return $url; + } + + // Drupal might not be bootstrapped if being called by the REST API. + if (!class_exists('Drupal') || !\Drupal::hasContainer()) { + return $url; + } + + $language = $this->getCurrentLanguage(); + if (\Drupal::service('module_handler')->moduleExists('language')) { + $config = \Drupal::config('language.negotiation')->get('url'); + + //does user configuration allow language + //support from the URL (Path prefix or domain) + $enabledLanguageMethods = \Drupal::config('language.types')->get('negotiation.language_interface.enabled') ?: []; + if (array_key_exists(\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID, $enabledLanguageMethods)) { + $urlType = $config['source']; + + //url prefix + if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::CONFIG_PATH_PREFIX) { + if (!empty($language)) { + if ($addLanguagePart && !empty($config['prefixes'][$language])) { + $url .= $config['prefixes'][$language] . '/'; + } + if ($removeLanguagePart) { + $url = str_replace("/" . $config['prefixes'][$language] . "/", '/', $url); + } + } + } + //domain + if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::CONFIG_DOMAIN) { + if (isset($language->domain) && $language->domain) { + if ($addLanguagePart) { + $url = (CRM_Utils_System::isSSL() ? 'https' : 'http') . '://' . $config['domains'][$language] . base_path(); + } + if ($removeLanguagePart && defined('CIVICRM_UF_BASEURL')) { + $url = str_replace('\\', '/', $url); + $parseUrl = parse_url($url); + + //kinda hackish but not sure how to do it right + //hope http_build_url() will help at some point. + if (is_array($parseUrl) && !empty($parseUrl)) { + $urlParts = explode('/', $url); + $hostKey = array_search($parseUrl['host'], $urlParts); + $ufUrlParts = parse_url(CIVICRM_UF_BASEURL); + $urlParts[$hostKey] = $ufUrlParts['host']; + $url = implode('/', $urlParts); + } + } + } + } + } + } + + return $url; + } + } diff --git a/CRM/Utils/System/DrupalBase.php b/CRM/Utils/System/DrupalBase.php index c0ce0f49c196..115efaebebbe 100644 --- a/CRM/Utils/System/DrupalBase.php +++ b/CRM/Utils/System/DrupalBase.php @@ -1,9 +1,9 @@ supports_form_extensions = TRUE; } + /** + * @inheritdoc + */ + public function getDefaultFileStorage() { + $config = CRM_Core_Config::singleton(); + $baseURL = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE); + + $siteName = $this->parseDrupalSiteNameFromRequest('/files/civicrm'); + if ($siteName) { + $filesURL = $baseURL . "sites/$siteName/files/civicrm/"; + } + else { + $filesURL = $baseURL . "sites/default/files/civicrm/"; + } + + return [ + 'url' => $filesURL, + 'path' => CRM_Utils_File::baseFilePath(), + ]; + } + /** * @inheritDoc */ public function getDefaultSiteSettings($dir) { $config = CRM_Core_Config::singleton(); $siteName = $siteRoot = NULL; - $matches = array(); + $matches = []; if (preg_match( '|/sites/([\w\.\-\_]+)/|', $config->templateCompileDir, @@ -80,7 +101,7 @@ public function getDefaultSiteSettings($dir) { } } $url = $config->userFrameworkBaseURL; - return array($url, $siteName, $siteRoot); + return [$url, $siteName, $siteRoot]; } /** @@ -96,26 +117,24 @@ public function formatResourceUrl(&$url) { $internal = FALSE; $base = CRM_Core_Config::singleton()->resourceBase; global $base_url; + // Strip query string + $q = strpos($url, '?'); + $url_path = $q ? substr($url, 0, $q) : $url; // Handle absolute urls // compares $url (which is some unknown/untrusted value from a third-party dev) to the CMS's base url (which is independent of civi's url) // to see if the url is within our drupal dir, if it is we are able to treated it as an internal url - if (strpos($url, $base_url) === 0) { - $file = trim(str_replace($base_url, '', $url), '/'); + if (strpos($url_path, $base_url) === 0) { + $file = trim(str_replace($base_url, '', $url_path), '/'); // CRM-18130: Custom CSS URL not working if aliased or rewritten - if (file_exists(DRUPAL_ROOT . $file)) { + if (file_exists(DRUPAL_ROOT . '/' . $file)) { $url = $file; $internal = TRUE; } } // Handle relative urls that are within the CiviCRM module directory - elseif (strpos($url, $base) === 0) { + elseif (strpos($url_path, $base) === 0) { $internal = TRUE; - $url = $this->appendCoreDirectoryToResourceBase(dirname(drupal_get_path('module', 'civicrm')) . '/') . trim(substr($url, strlen($base)), '/'); - } - // Strip query string - $q = strpos($url, '?'); - if ($q && $internal) { - $url = substr($url, 0, $q); + $url = $this->appendCoreDirectoryToResourceBase(dirname(drupal_get_path('module', 'civicrm')) . '/') . trim(substr($url_path, strlen($base)), '/'); } return $internal; } @@ -247,10 +266,10 @@ public function permissionDenied() { public function getUserRecordUrl($contactID) { $uid = CRM_Core_BAO_UFMatch::getUFId($contactID); if (CRM_Core_Session::singleton() - ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(array( - 'cms:administer users', - 'cms:view user account', - )) + ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm([ + 'cms:administer users', + 'cms:view user account', + ]) ) { return $this->url('user/' . $uid); }; @@ -268,7 +287,7 @@ public function checkPermissionAddUser() { */ public function logger($message) { if (CRM_Core_Config::singleton()->userFrameworkLogging && function_exists('watchdog')) { - watchdog('civicrm', '%message', array('%message' => $message), NULL, WATCHDOG_DEBUG); + watchdog('civicrm', '%message', ['%message' => $message], NULL, WATCHDOG_DEBUG); } } @@ -279,15 +298,6 @@ public function clearResourceCache() { _drupal_flush_css_js(); } - /** - * Append Drupal js to coreResourcesList. - * - * @param array $list - */ - public function appendCoreResources(&$list) { - $list[] = 'js/crm.drupal.js'; - } - /** * @inheritDoc */ @@ -299,7 +309,7 @@ public function flush() { * @inheritDoc */ public function getModules() { - $result = array(); + $result = []; $q = db_query('SELECT name, status FROM {system} WHERE type = \'module\' AND schema_version <> -1'); foreach ($q as $row) { $result[] = new CRM_Core_Module('drupal.' . $row->name, ($row->status == 1) ? TRUE : FALSE); @@ -321,7 +331,7 @@ public function replacePermission($oldPerm, $newPerms) { $roles = user_roles(FALSE, $oldPerm); if (!empty($roles)) { foreach (array_keys($roles) as $rid) { - user_role_revoke_permissions($rid, array($oldPerm)); + user_role_revoke_permissions($rid, [$oldPerm]); user_role_grant_permissions($rid, $newPerms); } } @@ -393,6 +403,26 @@ public function getVersion() { return defined('VERSION') ? VERSION : 'Unknown'; } + /** + * @inheritDoc + */ + public function isUserRegistrationPermitted() { + if (!variable_get('user_register', TRUE)) { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function isPasswordUserGenerated() { + if (variable_get('user_email_verification', TRUE)) { + return FALSE; + } + return TRUE; + } + /** * @inheritDoc */ @@ -409,25 +439,25 @@ public function getUFLocale() { // return CiviCRM’s xx_YY locale that either matches Drupal’s Chinese locale // (for CRM-6281), Drupal’s xx_YY or is retrieved based on Drupal’s xx // sometimes for CLI based on order called, this might not be set and/or empty - global $language; + $language = $this->getCurrentLanguage(); if (empty($language)) { return NULL; } - if ($language->language == 'zh-hans') { + if ($language == 'zh-hans') { return 'zh_CN'; } - if ($language->language == 'zh-hant') { + if ($language == 'zh-hant') { return 'zh_TW'; } - if (preg_match('/^.._..$/', $language->language)) { - return $language->language; + if (preg_match('/^.._..$/', $language)) { + return $language; } - return CRM_Core_I18n_PseudoConstant::longForShort(substr($language->language, 0, 2)); + return CRM_Core_I18n_PseudoConstant::longForShort(substr($language, 0, 2)); } /** @@ -462,7 +492,7 @@ public function setUFLocale($civicrm_language) { * * FIXME: Document values accepted/required by $params */ - public function userLoginFinalize($params = array()) { + public function userLoginFinalize($params = []) { user_login_finalize($params); } @@ -551,8 +581,9 @@ public function getUserObject($userID) { * @param string $civicrm_root * * @return null|string + * @deprecated */ - public function parseDrupalSiteName($civicrm_root) { + public function parseDrupalSiteNameFromRoot($civicrm_root) { $siteName = NULL; if (strpos($civicrm_root, DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . 'all' . DIRECTORY_SEPARATOR . 'modules' @@ -575,4 +606,81 @@ public function parseDrupalSiteName($civicrm_root) { return $siteName; } + /** + * Determine if Drupal multi-site applies to the current request -- and, + * specifically, determine the name of the multisite folder. + * + * @param string $flagFile + * Check if $flagFile exists inside the site dir. + * @return null|string + * string, e.g. `bar.example.com` if using multisite. + * NULL if using the default site. + */ + private function parseDrupalSiteNameFromRequest($flagFile = '') { + $phpSelf = array_key_exists('PHP_SELF', $_SERVER) ? $_SERVER['PHP_SELF'] : ''; + $httpHost = array_key_exists('HTTP_HOST', $_SERVER) ? $_SERVER['HTTP_HOST'] : ''; + if (empty($httpHost)) { + $httpHost = parse_url(CIVICRM_UF_BASEURL, PHP_URL_HOST); + if (parse_url(CIVICRM_UF_BASEURL, PHP_URL_PORT)) { + $httpHost .= ':' . parse_url(CIVICRM_UF_BASEURL, PHP_URL_PORT); + } + } + + $confdir = $this->cmsRootPath() . '/sites'; + + if (file_exists($confdir . "/sites.php")) { + include $confdir . "/sites.php"; + } + else { + $sites = []; + } + + $uri = explode('/', $phpSelf); + $server = explode('.', implode('.', array_reverse(explode(':', rtrim($httpHost, '.'))))); + for ($i = count($uri) - 1; $i > 0; $i--) { + for ($j = count($server); $j > 0; $j--) { + $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); + if (file_exists("$confdir/$dir" . $flagFile)) { + \Civi::$statics[__CLASS__]['drupalSiteName'] = $dir; + return \Civi::$statics[__CLASS__]['drupalSiteName']; + } + // check for alias + if (isset($sites[$dir]) && file_exists("$confdir/{$sites[$dir]}" . $flagFile)) { + \Civi::$statics[__CLASS__]['drupalSiteName'] = $sites[$dir]; + return \Civi::$statics[__CLASS__]['drupalSiteName']; + } + } + } + } + + /** + * Function to return current language of Drupal + * + * @return string + */ + public function getCurrentLanguage() { + global $language; + return (!empty($language->language)) ? $language->language : $language; + } + + /** + * Is a front end page being accessed. + * + * Generally this would be a contribution form or other public page as opposed to a backoffice page (like contact edit). + * + * See https://github.com/civicrm/civicrm-drupal/pull/546/files + * + * @return bool + */ + public function isFrontEndPage() { + $path = CRM_Utils_System::getUrlPath(); + + // Get the menu for above URL. + $item = CRM_Core_Menu::get($path); + if (!empty(CRM_Utils_Array::value('is_public', $item))) { + return TRUE; + } + return FALSE; + } + } diff --git a/CRM/Utils/System/Joomla.php b/CRM/Utils/System/Joomla.php index 8162ac8a615c..746ef630ecee 100644 --- a/CRM/Utils/System/Joomla.php +++ b/CRM/Utils/System/Joomla.php @@ -1,9 +1,9 @@ setQuery($query, 0, 10); $users = $db->loadAssocList(); - $row = array(); + $row = []; if (count($users)) { $row = $users[0]; } @@ -152,13 +153,13 @@ public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email $dbEmail = CRM_Utils_Array::value('email', $row); if (strtolower($dbName) == strtolower($name)) { $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', - array(1 => $name) + [1 => $name] ); } if (strtolower($dbEmail) == strtolower($email)) { $resetUrl = str_replace('administrator/', '', $config->userFrameworkBaseURL) . 'index.php?option=com_users&view=reset'; $errors[$emailName] = ts('The email address %1 already has an account associated with it. Have you forgotten your password?', - array(1 => $email, 2 => $resetUrl) + [1 => $email, 2 => $resetUrl] ); } } @@ -189,7 +190,7 @@ public function appendBreadCrumb($breadCrumbs) { if (is_array($breadCrumbs)) { foreach ($breadCrumbs as $crumbs) { if (stripos($crumbs['url'], 'id%%')) { - $args = array('cid', 'mid'); + $args = ['cid', 'mid']; foreach ($args as $a) { $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, $_GET @@ -264,8 +265,12 @@ public function url( if ($config->userFrameworkFrontend) { $script = 'index.php'; - if (JRequest::getVar("Itemid")) { - $Itemid = "{$separator}Itemid=" . JRequest::getVar("Itemid"); + + // Get Itemid using JInput::get() + $input = Joomla\CMS\Factory::getApplication()->input; + $itemIdNum = $input->get("Itemid"); + if ($itemIdNum && (strpos($path, 'civicrm/payment/ipn') === FALSE)) { + $Itemid = "{$separator}Itemid=" . $itemIdNum; } } @@ -317,8 +322,8 @@ public function setEmail(&$user) { global $database; $query = $db->getQuery(TRUE); $query->select($db->quoteName('email')) - ->from($db->quoteName('#__users')) - ->where($db->quoteName('id') . ' = ' . $user->id); + ->from($db->quoteName('#__users')) + ->where($db->quoteName('id') . ' = ' . $user->id); $database->setQuery($query); $user->email = $database->loadResult(); } @@ -333,12 +338,12 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP $user = NULL; if ($loadCMSBootstrap) { - $bootStrapParams = array(); + $bootStrapParams = []; if ($name && $password) { - $bootStrapParams = array( + $bootStrapParams = [ 'name' => $name, 'pass' => $password, - ); + ]; } CRM_Utils_System::loadBootStrap($bootStrapParams, TRUE, TRUE, FALSE); } @@ -357,17 +362,13 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP $db->setQuery($query, 0, 0); $users = $db->loadObjectList(); - $row = array(); + $row = []; if (count($users)) { $row = $users[0]; } - $joomlaBase = dirname(dirname(dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))))); - if (!defined('JVERSION')) { - require $joomlaBase . '/libraries/cms/version/version.php'; - $jversion = new JVersion(); - define('JVERSION', $jversion->getShortVersion()); - } + $joomlaBase = self::getBasePath(); + self::getJVersion($joomlaBase); if (!empty($row)) { $dbPassword = $row->password; @@ -389,8 +390,13 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP return FALSE; } + if (version_compare(JVERSION, '3.8.0', 'ge')) { + jimport('joomla.application.helper'); + jimport('joomla.application.cms'); + jimport('joomla.application.administrator'); + } //include additional files required by Joomla 3.2.1+ - if (version_compare(JVERSION, '3.2.1', 'ge')) { + elseif (version_compare(JVERSION, '3.2.1', 'ge')) { require_once $joomlaBase . '/libraries/cms/application/helper.php'; require_once $joomlaBase . '/libraries/cms/application/cms.php'; require_once $joomlaBase . '/libraries/cms/application/administrator.php'; @@ -402,7 +408,7 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP if (!$contactID) { return FALSE; } - return array($contactID, $dbId, mt_rand()); + return [$contactID, $dbId, mt_rand()]; } return FALSE; @@ -445,14 +451,20 @@ public function loadUser($username, $password = NULL) { $contactID = CRM_Core_BAO_UFMatch::getContactId($uid); if (!empty($password)) { $instance = JFactory::getApplication('site'); - $params = array( + $params = [ 'username' => $username, 'password' => $password, - ); + ]; //perform the login action $instance->login($params); } + // Save details in Joomla session + $user = JFactory::getUser($uid); + $jsession = JFactory::getSession(); + $jsession->set('user', $user); + + // Save details in Civi session $session = CRM_Core_Session::singleton(); $session->set('ufID', $uid); $session->set('userID', $contactID); @@ -507,6 +519,32 @@ public function getVersion() { } } + public function getJVersion($joomlaBase) { + // Files may be in different places depending on Joomla version + if (!defined('JVERSION')) { + // Joomla 3.8.0+ + $versionPhp = $joomlaBase . '/libraries/src/Version.php'; + if (!file_exists($versionPhp)) { + // Joomla < 3.8.0 + $versionPhp = $joomlaBase . '/libraries/cms/version/version.php'; + } + require $versionPhp; + $jversion = new JVersion(); + define('JVERSION', $jversion->getShortVersion()); + } + } + + /** + * Setup the base path related constant. + * @return mixed + */ + public function getBasePath() { + global $civicrm_root; + $joomlaPath = explode(DIRECTORY_SEPARATOR . 'administrator', $civicrm_root); + $joomlaBase = $joomlaPath[0]; + return $joomlaBase; + } + /** * Load joomla bootstrap. * @@ -520,9 +558,8 @@ public function getVersion() { * * @return bool */ - public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL, $loadDefines = TRUE) { - // Setup the base path related constant. - $joomlaBase = dirname(dirname(dirname(dirname(dirname(dirname(dirname(dirname(__FILE__)))))))); + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL, $loadDefines = TRUE) { + $joomlaBase = self::getBasePath(); // load BootStrap here if needed // We are a valid Joomla entry point. @@ -537,27 +574,39 @@ public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = if (file_exists($joomlaBase . '/libraries/import.legacy.php')) { require $joomlaBase . '/libraries/import.legacy.php'; } - require $joomlaBase . '/libraries/import.php'; - require $joomlaBase . '/libraries/joomla/event/dispatcher.php'; - require $joomlaBase . '/configuration.php'; + require $joomlaBase . '/libraries/cms.php'; + self::getJVersion($joomlaBase); - // Files may be in different places depending on Joomla version - if (!defined('JVERSION')) { - require $joomlaBase . '/libraries/cms/version/version.php'; - $jversion = new JVersion(); - define('JVERSION', $jversion->getShortVersion()); + if (version_compare(JVERSION, '3.8', 'lt')) { + require $joomlaBase . '/libraries/import.php'; + require $joomlaBase . '/libraries/joomla/event/dispatcher.php'; } + require_once $joomlaBase . '/configuration.php'; + if (version_compare(JVERSION, '3.0', 'lt')) { require $joomlaBase . '/libraries/joomla/environment/uri.php'; require $joomlaBase . '/libraries/joomla/application/component/helper.php'; } - else { - require $joomlaBase . '/libraries/cms.php'; - require $joomlaBase . '/libraries/joomla/uri/uri.php'; + elseif (version_compare(JVERSION, '3.8', 'lt')) { + jimport('joomla.environment.uri'); } - jimport('joomla.application.cli'); + if (version_compare(JVERSION, '3.8', 'lt')) { + jimport('joomla.application.cli'); + } + + if (!defined('JDEBUG')) { + define('JDEBUG', FALSE); + } + + // Set timezone for Joomla on Cron + $config = JFactory::getConfig(); + $timezone = $config->get('offset'); + if ($timezone) { + date_default_timezone_set($timezone); + CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone(); + } // CRM-14281 Joomla wasn't available during bootstrap, so hook_civicrm_config never executes. $config = CRM_Core_Config::singleton(); @@ -574,6 +623,24 @@ public function isUserLoggedIn() { return ($user->guest) ? FALSE : TRUE; } + /** + * @inheritDoc + */ + public function isUserRegistrationPermitted() { + $userParams = JComponentHelper::getParams('com_users'); + if (!$userParams->get('allowUserRegistration')) { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function isPasswordUserGenerated() { + return TRUE; + } + /** * @inheritDoc */ @@ -590,6 +657,16 @@ public function getLoggedInUniqueIdentifier() { return $this->getUniqueIdentifierFromUserObject($user); } + /** + * @inheritDoc + */ + public function getUser($contactID) { + $user_details = parent::getUser($contactID); + $user = JFactory::getUser($user_details['id']); + $user_details['name'] = $user->name; + return $user_details; + } + /** * @inheritDoc */ @@ -619,7 +696,7 @@ public function getTimeZoneString() { * CRM_Core_Module */ public function getModules() { - $result = array(); + $result = []; $db = JFactory::getDbo(); $query = $db->getQuery(TRUE); @@ -629,7 +706,7 @@ public function getModules() { $plugins = $db->setQuery($query)->loadAssocList(); foreach ($plugins as $plugin) { // question: is the folder really a critical part of the plugin's name? - $name = implode('.', array('joomla', $plugin['type'], $plugin['folder'], $plugin['element'])); + $name = implode('.', ['joomla', $plugin['type'], $plugin['folder'], $plugin['element']]); $result[] = new CRM_Core_Module($name, $plugin['enabled'] ? TRUE : FALSE); } @@ -694,9 +771,13 @@ public function getLoginDestination(&$form) { * local file system path to CMS root, or NULL if it cannot be determined */ public function cmsRootPath() { + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + list($url, $siteName, $siteRoot) = $this->getDefaultSiteSettings(); - $includePath = "$siteRoot/libraries/cms/version"; - if (file_exists("$includePath/version.php")) { + if (file_exists("$siteRoot/administrator/index.php")) { return $siteRoot; } return NULL; @@ -729,7 +810,7 @@ public function getDefaultSiteSettings($dir = NULL) { '', $config->imageUploadDir ); - return array($url, NULL, $siteRoot); + return [$url, NULL, $siteRoot]; } /** @@ -756,32 +837,6 @@ public function checkPermissionAddUser() { } } - /** - * Output code from error function. - * @param string $content - */ - public function outputError($content) { - if (class_exists('JErrorPage')) { - $error = new Exception($content); - JErrorPage::render($error); - } - elseif (class_exists('JError')) { - JError::raiseError('CiviCRM-001', $content); - } - else { - parent::outputError($content); - } - } - - /** - * Append Joomla js to coreResourcesList. - * - * @param array $list - */ - public function appendCoreResources(&$list) { - $list[] = 'js/crm.joomla.js'; - } - /** * @inheritDoc */ @@ -829,16 +884,13 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; } } diff --git a/CRM/Utils/System/Soap.php b/CRM/Utils/System/Soap.php index b6233ae98660..2edca70da7be 100644 --- a/CRM/Utils/System/Soap.php +++ b/CRM/Utils/System/Soap.php @@ -1,9 +1,9 @@ supports_form_extensions = FALSE; } + /** + * @param string $name + * @param string $value + */ + public function setHttpHeader($name, $value) { + Civi::$statics[__CLASS__]['header'][] = ("$name: $value"); + } + /** * @inheritDoc */ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) { - $retVal = array(1, 1, 12345); + $retVal = [1, 1, 12345]; return $retVal; } /** * Bootstrap the phony CMS. - * - * @param string $name - * Optional username for login. - * @param string $pass - * Optional password for login. - * - * @return bool */ - public function loadBootStrap($name = NULL, $pass = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { return TRUE; } diff --git a/CRM/Utils/System/WordPress.php b/CRM/Utils/System/WordPress.php index d49c7d77b470..382892d314b6 100644 --- a/CRM/Utils/System/WordPress.php +++ b/CRM/Utils/System/WordPress.php @@ -1,9 +1,9 @@ CRM_Utils_File::addTrailingSlash($filesURL, '/'), 'path' => CRM_Utils_File::addTrailingSlash($filesPath), - ); + ]; } /** @@ -114,10 +115,10 @@ public function getCiviSourceStorage() { } $civiRelPath = CRM_Utils_File::relativize(realpath($civicrm_root), realpath($cmsPath)); $civiUrl = rtrim($cmsUrl, '/') . '/' . ltrim($civiRelPath, ' /'); - return array( + return [ 'url' => CRM_Utils_File::addTrailingSlash($civiUrl, '/'), 'path' => CRM_Utils_File::addTrailingSlash($civicrm_root), - ); + ]; } /** @@ -129,7 +130,7 @@ public function appendBreadCrumb($breadCrumbs) { if (is_array($breadCrumbs)) { foreach ($breadCrumbs as $crumbs) { if (stripos($crumbs['url'], 'id%%')) { - $args = array('cid', 'mid'); + $args = ['cid', 'mid']; foreach ($args as $a) { $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject, FALSE, NULL, $_GET @@ -152,7 +153,7 @@ public function appendBreadCrumb($breadCrumbs) { * @inheritDoc */ public function resetBreadCrumb() { - $bc = array(); + $bc = []; wp_set_breadcrumb($bc); } @@ -163,13 +164,13 @@ public function addHTMLHead($head) { static $registered = FALSE; if (!$registered) { // front-end view - add_action('wp_head', array(__CLASS__, '_showHTMLHead')); + add_action('wp_head', [__CLASS__, '_showHTMLHead']); // back-end views - add_action('admin_head', array(__CLASS__, '_showHTMLHead')); + add_action('admin_head', [__CLASS__, '_showHTMLHead']); } - CRM_Core_Region::instance('wp_head')->add(array( + CRM_Core_Region::instance('wp_head')->add([ 'markup' => $head, - )); + ]); } /** @@ -208,6 +209,7 @@ public function url( $fragment = isset($fragment) ? ('#' . $fragment) : ''; $path = CRM_Utils_String::stripPathChars($path); + $basepage = FALSE; //this means wp function we are trying to use is not available, //so load bootStrap @@ -215,16 +217,20 @@ public function url( if (!function_exists('get_option')) { $this->loadBootStrap(); } + if ($config->userFrameworkFrontend) { + global $post; if (get_option('permalink_structure') != '') { - global $post; $script = get_permalink($post->ID); } - + if ($config->wpBasePage == $post->post_name) { + $basepage = TRUE; + } // when shortcode is included in page // also make sure we have valid query object + // FIXME: $wpPageParam has no effect and is only set on the *basepage* global $wp_query; - if (method_exists($wp_query, 'get')) { + if (get_option('permalink_structure') == '' && method_exists($wp_query, 'get')) { if (get_query_var('page_id')) { $wpPageParam = "page_id=" . get_query_var('page_id'); } @@ -250,22 +256,62 @@ public function url( $base = $script; } - $queryParts = array(); - if (isset($path)) { - $queryParts[] = 'page=CiviCRM'; - $queryParts[] = "q={$path}"; - } - if ($wpPageParam) { - $queryParts[] = $wpPageParam; + $queryParts = []; + + if ( + // not using clean URLs + !$config->cleanURL + // requesting an admin URL + || ((is_admin() && !$frontend) || $forceBackend) + // is shortcode + || (!$basepage && $script != '') + ) { + + // pre-existing logic + if (isset($path)) { + $queryParts[] = 'page=CiviCRM'; + $queryParts[] = 'q=' . rawurlencode($path); + } + if ($wpPageParam) { + $queryParts[] = $wpPageParam; + } + if (!empty($query)) { + $queryParts[] = $query; + } + + $final = $base . '?' . implode($separator, $queryParts) . $fragment; + } - if (isset($query)) { - $queryParts[] = $query; + else { + + // clean URLs + if (isset($path)) { + $base = trailingslashit($base) . str_replace('civicrm/', '', $path) . '/'; + } + if (isset($query)) { + $query = ltrim($query, '=?&'); + $queryParts[] = $query; + } + + if (!empty($queryParts)) { + $final = $base . '?' . implode($separator, $queryParts) . $fragment; + } + else { + $final = $base . $fragment; + } + } - return $base . '?' . implode($separator, $queryParts) . $fragment; + return $final; } /** + * 27-09-2016 + * CRM-16421 CRM-17633 WIP Changes to support WP in it's own directory + * https://wiki.civicrm.org/confluence/display/CRM/WordPress+installed+in+its+own+directory+issues + * For now leave hard coded wp-admin references. + * TODO: remove wp-admin references and replace with admin_url() in the future. Look at best way to get path to admin_url + * * @param $absolute * @param $frontend * @param $forceBackend @@ -274,22 +320,12 @@ public function url( */ private function getBaseUrl($absolute, $frontend, $forceBackend) { $config = CRM_Core_Config::singleton(); - - $base = $absolute ? $config->userFrameworkBaseURL : $config->useFrameworkRelativeBase; - if ((is_admin() && !$frontend) || $forceBackend) { - $base .= 'wp-admin/admin.php'; - return $base; + return Civi::paths()->getUrl('[wp.backend]/.', $absolute ? 'absolute' : 'relative'); } - elseif (defined('CIVICRM_UF_WP_BASEPAGE')) { - $base .= CIVICRM_UF_WP_BASEPAGE; - return $base; - } - elseif (isset($config->wpBasePage)) { - $base .= $config->wpBasePage; - return $base; + else { + return Civi::paths()->getUrl('[wp.frontend]/.', $absolute ? 'absolute' : 'relative'); } - return $base; } /** @@ -299,7 +335,10 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP $config = CRM_Core_Config::singleton(); if ($loadCMSBootstrap) { - $config->userSystem->loadBootStrap($name, $password); + $config->userSystem->loadBootStrap([ + 'name' => $name, + 'pass' => $password, + ]); } $user = wp_authenticate($name, $password); @@ -314,7 +353,7 @@ public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realP if (!$contactID) { return FALSE; } - return array($contactID, $user->data->ID, mt_rand()); + return [$contactID, $user->data->ID, mt_rand()]; } /** @@ -354,6 +393,21 @@ public function permissionDenied() { CRM_Core_Error::fatal(ts('You do not have permission to access this page.')); } + /** + * Determine the native ID of the CMS user. + * + * @param string $username + * + * @return int|null + */ + public function getUfId($username) { + $userdata = get_user_by('login', $username); + if (!$userdata->data->ID) { + return NULL; + } + return $userdata->data->ID; + } + /** * @inheritDoc */ @@ -370,14 +424,18 @@ public function logout() { * @inheritDoc */ public function getUFLocale() { + // Polylang plugin + if (function_exists('pll_current_language')) { + $language = pll_current_language(); + } // WPML plugin - if (defined('ICL_LANGUAGE_CODE')) { + elseif (defined('ICL_LANGUAGE_CODE')) { $language = ICL_LANGUAGE_CODE; } // TODO: set language variable for others WordPress plugin - if (isset($language)) { + if (!empty($language)) { return CRM_Core_I18n_PseudoConstant::longForShort(substr($language, 0, 2)); } else { @@ -396,16 +454,22 @@ public function setUFLocale($civicrm_language) { /** * Load wordpress bootstrap. * - * @param string $name - * optional username for login. - * @param string $pass - * optional password for login. + * @param array $params + * Optional credentials + * - name: string, cms username + * - pass: string, cms password + * @param bool $loadUser + * @param bool $throwError + * @param mixed $realPath * * @return bool */ - public function loadBootStrap($name = NULL, $pass = NULL) { + public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) { global $wp, $wp_rewrite, $wp_the_query, $wp_query, $wpdb, $current_site, $current_blog, $current_user; + $name = CRM_Utils_Array::value('name', $params); + $pass = CRM_Utils_Array::value('pass', $params); + if (!defined('WP_USE_THEMES')) { define('WP_USE_THEMES', FALSE); } @@ -430,7 +494,7 @@ public function loadBootStrap($name = NULL, $pass = NULL) { CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone(); } require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-includes/pluggable.php'; - $uid = CRM_Utils_Array::value('uid', $name); + $uid = CRM_Utils_Array::value('uid', $params); if (!$uid) { $name = $name ? $name : trim(CRM_Utils_Array::value('name', $_REQUEST)); $pass = $pass ? $pass : trim(CRM_Utils_Array::value('pass', $_REQUEST)); @@ -468,7 +532,7 @@ public function loadBootStrap($name = NULL, $pass = NULL) { */ public function validInstallDir($dir) { $includePath = "$dir/wp-includes"; - if (file_exists("$includePath/version.php")) { + if (@file_exists("$includePath/version.php")) { return TRUE; } return FALSE; @@ -481,6 +545,11 @@ public function validInstallDir($dir) { * local file system path to CMS root, or NULL if it cannot be determined */ public function cmsRootPath() { + global $civicrm_paths; + if (!empty($civicrm_paths['cms.root']['path'])) { + return $civicrm_paths['cms.root']['path']; + } + $cmsRoot = $valid = NULL; if (defined('CIVICRM_CMSDIR')) { if ($this->validInstallDir(CIVICRM_CMSDIR)) { @@ -515,14 +584,14 @@ public function cmsRootPath() { * @inheritDoc */ public function createUser(&$params, $mail) { - $user_data = array( + $user_data = [ 'ID' => '', 'user_pass' => $params['cms_pass'], 'user_login' => $params['cms_name'], 'user_email' => $params[$mail], 'nickname' => $params['cms_name'], 'role' => get_option('default_role'), - ); + ]; if (isset($params['contactID'])) { $contactType = CRM_Contact_BAO_Contact::getContactType($params['contactID']); if ($contactType == 'Individual') { @@ -537,7 +606,7 @@ public function createUser(&$params, $mail) { $uid = wp_insert_user($user_data); - $creds = array(); + $creds = []; $creds['user_login'] = $params['cms_name']; $creds['user_password'] = $params['cms_pass']; $creds['remember'] = TRUE; @@ -556,7 +625,7 @@ public function updateCMSName($ufID, $ufName) { $ufID = CRM_Utils_Type::escape($ufID, 'Integer'); $ufName = CRM_Utils_Type::escape($ufName, 'String'); - $values = array('ID' => $ufID, 'user_email' => $ufName); + $values = ['ID' => $ufID, 'user_email' => $ufName]; if ($ufID) { wp_update_user($values); } @@ -580,7 +649,7 @@ public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email $errors['cms_name'] = ts("Your username contains invalid characters"); } elseif (username_exists(sanitize_user($params['name']))) { - $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', array(1 => $params['name'])); + $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', [1 => $params['name']]); } } @@ -590,7 +659,7 @@ public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email } elseif (email_exists($params['mail'])) { $errors[$emailName] = ts('The email address %1 already has an account associated with it. Have you forgotten your password?', - array(1 => $params['mail'], 2 => wp_lostpassword_url()) + [1 => $params['mail'], 2 => wp_lostpassword_url()] ); } } @@ -608,6 +677,23 @@ public function isUserLoggedIn() { return $isloggedIn; } + /** + * @inheritDoc + */ + public function isUserRegistrationPermitted() { + if (!get_option('users_can_register')) { + return FALSE; + } + return TRUE; + } + + /** + * @inheritDoc + */ + public function isPasswordUserGenerated() { + return TRUE; + } + /** * @return mixed */ @@ -700,7 +786,7 @@ public function getTimeZoneString() { public function getUserRecordUrl($contactID) { $uid = CRM_Core_BAO_UFMatch::getUFId($contactID); if (CRM_Core_Session::singleton() - ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(array('cms:administer users')) + ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(['cms:administer users']) ) { return CRM_Core_Config::singleton()->userFrameworkBaseURL . "wp-admin/user-edit.php?user_id=" . $uid; } @@ -709,10 +795,20 @@ public function getUserRecordUrl($contactID) { /** * Append WP js to coreResourcesList. * - * @param array $list + * @param \Civi\Core\Event\GenericHookEvent $e */ - public function appendCoreResources(&$list) { - $list[] = 'js/crm.wordpress.js'; + public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) { + $e->list[] = 'js/crm.wordpress.js'; + } + + /** + * @inheritDoc + */ + public function alterAssetUrl(\Civi\Core\Event\GenericHookEvent $e) { + // Set menubar breakpoint to match WP admin theme + if ($e->asset == 'crm-menubar.css') { + $e->params['breakpoint'] = 783; + } } /** @@ -751,16 +847,28 @@ public function synchronizeUsers() { else { $contactMatching++; } - if (is_object($match)) { - $match->free(); - } } - return array( + return [ 'contactCount' => $contactCount, 'contactMatching' => $contactMatching, 'contactCreated' => $contactCreated, - ); + ]; + } + + /** + * Send an HTTP Response base on PSR HTTP RespnseInterface response. + * + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function sendResponse(\Psr\Http\Message\ResponseInterface $response) { + // use WordPress function status_header to ensure 404 response is sent + status_header($response->getStatusCode()); + foreach ($response->getHeaders() as $name => $values) { + CRM_Utils_System::setHttpHeader($name, implode(', ', (array) $values)); + } + echo $response->getBody(); + CRM_Utils_System::civiExit(); } } diff --git a/CRM/Utils/SystemLogger.php b/CRM/Utils/SystemLogger.php index dd6ba99546f9..641058894140 100644 --- a/CRM/Utils/SystemLogger.php +++ b/CRM/Utils/SystemLogger.php @@ -1,9 +1,9 @@ {$separateField} = $context[$separateField]; diff --git a/CRM/Utils/Time.php b/CRM/Utils/Time.php index 639f234d9b40..bf4dd44255f9 100644 --- a/CRM/Utils/Time.php +++ b/CRM/Utils/Time.php @@ -1,9 +1,9 @@ array( + public static $_tokens = [ + 'action' => [ 'forward', 'optOut', 'optOutUrl', @@ -48,8 +48,8 @@ class CRM_Utils_Token { 'resubscribe', 'resubscribeUrl', 'subscribeUrl', - ), - 'mailing' => array( + ], + 'mailing' => [ 'id', 'name', 'group', @@ -62,47 +62,48 @@ class CRM_Utils_Token { 'approveUrl', 'creator', 'creatorEmail', - ), - 'user' => array( + ], + 'user' => [ // we extract the stuff after the role / permission and return the // civicrm email addresses of all users with that role / permission // useful with rules integration 'permission:', 'role:', - ), + ], // populate this dynamically 'contact' => NULL, // populate this dynamically 'contribution' => NULL, - 'domain' => array( + 'domain' => [ 'name', 'phone', 'address', 'email', 'id', 'description', - ), - 'subscribe' => array('group'), - 'unsubscribe' => array('group'), - 'resubscribe' => array('group'), - 'welcome' => array('group'), - ); - + ], + 'subscribe' => ['group'], + 'unsubscribe' => ['group'], + 'resubscribe' => ['group'], + 'welcome' => ['group'], + ]; /** + * @deprecated + * This is used by CiviMail but will be made redundant by FlexMailer. * @return array */ public static function getRequiredTokens() { if (self::$_requiredTokens == NULL) { - self::$_requiredTokens = array( + self::$_requiredTokens = [ 'domain.address' => ts("Domain address - displays your organization's postal address."), - 'action.optOutUrl or action.unsubscribeUrl' => array( + 'action.optOutUrl or action.unsubscribeUrl' => [ 'action.optOut' => ts("'Opt out via email' - displays an email address for recipients to opt out of receiving emails from your organization."), 'action.optOutUrl' => ts("'Opt out via web page' - creates a link for recipients to click if they want to opt out of receiving emails from your organization. Alternatively, you can include the 'Opt out via email' token."), 'action.unsubscribe' => ts("'Unsubscribe via email' - displays an email address for recipients to unsubscribe from the specific mailing list used to send this message."), 'action.unsubscribeUrl' => ts("'Unsubscribe via web page' - creates a link for recipients to unsubscribe from the specific mailing list used to send this message. Alternatively, you can include the 'Unsubscribe via email' token or one of the Opt-out tokens."), - ), - ); + ], + ]; } return self::$_requiredTokens; } @@ -114,13 +115,14 @@ public static function getRequiredTokens() { * The message. * * @return bool|array - * true if all required tokens are found, + * true if all required tokens are found, * else an array of the missing tokens */ public static function requiredTokens(&$str) { - $requiredTokens = self::getRequiredTokens(); + // FlexMailer is a refactoring of CiviMail which provides new hooks/APIs/docs. If the sysadmin has opted to enable it, then use that instead of CiviMail. + $requiredTokens = defined('CIVICRM_FLEXMAILER_HACK_REQUIRED_TOKENS') ? Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_REQUIRED_TOKENS, []) : CRM_Utils_Token::getRequiredTokens(); - $missing = array(); + $missing = []; foreach ($requiredTokens as $token => $value) { if (!is_array($value)) { if (!preg_match('/(^|[^\{])' . preg_quote('{' . $token . '}') . '/', $str)) { @@ -175,7 +177,7 @@ public static function token_match($type, $var, &$str) { * The token variable. * @param string $value * The value to substitute for the token. - * @param string (reference) $str The string to replace in + * @param string $str (reference) The string to replace in * * @param bool $escapeSmarty * @@ -218,7 +220,7 @@ private static function tokenRegex($token_type) { */ public static function tokenEscapeSmarty($string) { // need to use negative look-behind, as both str_replace() and preg_replace() are sequential - return preg_replace(array('/{/', '/(?getGroupNames() : array('Mailing Groups'); + $groups = $mailing ? $mailing->getGroupNames() : ['Mailing Groups']; $value = implode(', ', $groups); break; @@ -657,17 +659,14 @@ public static function &replaceContactTokens( $returnBlankToken = FALSE, $escapeSmarty = FALSE ) { - $key = 'contact'; - if (self::$_tokens[$key] == NULL) { - // This should come from UF - - self::$_tokens[$key] - = array_merge( - array_keys(CRM_Contact_BAO_Contact::exportableFields('All')), - array('checksum', 'contact_id') - ); - } + // Refresh contact tokens in case they have changed. There is heavy caching + // in exportable fields so there is no benefit in doing this conditionally. + self::$_tokens['contact'] = array_merge( + array_keys(CRM_Contact_BAO_Contact::exportableFields('All')), + ['checksum', 'contact_id'] + ); + $key = 'contact'; // here we intersect with the list of pre-configured valid tokens // so that we remove anything we do not recognize // I hope to move this step out of here soon and @@ -710,7 +709,7 @@ public static function getContactTokenReplacement( self::$_tokens['contact'] = array_merge( array_keys(CRM_Contact_BAO_Contact::exportableFields('All')), - array('checksum', 'contact_id') + ['checksum', 'contact_id'] ); } @@ -1104,8 +1103,8 @@ public static function &replaceComponentTokens(&$str, $contact, $components, $es * array of tokens mentioned in field */ public static function getTokens($string) { - $matches = array(); - $tokens = array(); + $matches = []; + $tokens = []; preg_match_all('/(? $contactID) { - $params[] = array( + $params = []; + foreach ($contactIDs as $contactID) { + $params[] = [ CRM_Core_Form::CB_PREFIX . $contactID, '=', 1, 0, 0, - ); + ]; } // fix for CRM-2613 if ($skipDeceased) { - $params[] = array('is_deceased', '=', 0, 0, 0); + $params[] = ['is_deceased', '=', 0, 0, 0]; } //fix for CRM-3798 if ($skipOnHold) { - $params[] = array('on_hold', '=', 0, 0, 0); + $params[] = ['on_hold', '=', 0, 0, 0]; } if ($extraParams) { @@ -1213,21 +1212,21 @@ public static function getTokenDetails( // if return properties are not passed then get all return properties if (empty($returnProperties)) { $fields = array_merge(array_keys(CRM_Contact_BAO_Contact::exportableFields()), - array('display_name', 'checksum', 'contact_id') + ['display_name', 'checksum', 'contact_id'] ); - foreach ($fields as $key => $val) { + foreach ($fields as $val) { // The unavailable fields are not available as tokens, do not have a one-2-one relationship // with contacts and are expensive to resolve. // @todo see CRM-17253 - there are some other fields (e.g note) that should be excluded // and upstream calls to this should populate return properties. - $unavailableFields = array('group', 'tag'); + $unavailableFields = ['group', 'tag']; if (!in_array($val, $unavailableFields)) { $returnProperties[$val] = 1; } } } - $custom = array(); + $custom = []; foreach ($returnProperties as $name => $dontCare) { $cfID = CRM_Core_BAO_CustomField::getKeyID($name); if ($cfID) { @@ -1235,18 +1234,16 @@ public static function getTokenDetails( } } - $query = new CRM_Contact_BAO_Query($params, $returnProperties); - - $details = $query->apiQuery($params, $returnProperties, NULL, NULL, 0, count($contactIDs)); + $details = CRM_Contact_BAO_Query::apiQuery($params, $returnProperties, NULL, NULL, 0, count($contactIDs), TRUE, FALSE, TRUE, CRM_Contact_BAO_Query::MODE_CONTACTS, NULL, TRUE); $contactDetails = &$details[0]; - foreach ($contactIDs as $key => $contactID) { + foreach ($contactIDs as $contactID) { if (array_key_exists($contactID, $contactDetails)) { if (!empty($contactDetails[$contactID]['preferred_communication_method']) ) { - $communicationPreferences = array(); - foreach ($contactDetails[$contactID]['preferred_communication_method'] as $key => $val) { + $communicationPreferences = []; + foreach ($contactDetails[$contactID]['preferred_communication_method'] as $val) { if ($val) { $communicationPreferences[$val] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'preferred_communication_method', $val); } @@ -1261,11 +1258,11 @@ public static function getTokenDetails( } // special case for greeting replacement - foreach (array( - 'email_greeting', - 'postal_greeting', - 'addressee', - ) as $val) { + foreach ([ + 'email_greeting', + 'postal_greeting', + 'addressee', + ] as $val) { if (!empty($contactDetails[$contactID][$val])) { $contactDetails[$contactID][$val] = $contactDetails[$contactID]["{$val}_display"]; } @@ -1301,17 +1298,17 @@ public static function getTokenDetails( * @return array * contactDetails with hooks swapped out */ - public static function getAnonymousTokenDetails($contactIDs = array( - 0, - ), + public static function getAnonymousTokenDetails($contactIDs = [ + 0, + ], $returnProperties = NULL, $skipOnHold = TRUE, $skipDeceased = TRUE, $extraParams = NULL, - $tokens = array(), + $tokens = [], $className = NULL, $jobID = NULL) { - $details = array(0 => array()); + $details = [0 => []]; // also call a hook and get token details CRM_Utils_Hook::tokenValues($details[0], $contactIDs, @@ -1328,10 +1325,10 @@ public static function getAnonymousTokenDetails($contactIDs = array( * Array of membership IDS. */ public static function getMembershipTokenDetails($membershipIDs) { - $memberships = civicrm_api3('membership', 'get', array( - 'options' => array('limit' => 0), - 'membership_id' => array('IN' => (array) $membershipIDs), - )); + $memberships = civicrm_api3('membership', 'get', [ + 'options' => ['limit' => 0], + 'membership_id' => ['IN' => (array) $membershipIDs], + ]); return $memberships['values']; } @@ -1368,6 +1365,7 @@ public static function replaceGreetingTokens(&$tokenString, $contactDetails = NU $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $contactDetails, TRUE, $greetingTokens, TRUE, $escapeSmarty); } + self::removeNullContactTokens($tokenString, $contactDetails, $greetingTokens); // check if there are any unevaluated tokens $greetingTokens = self::getTokens($tokenString); @@ -1377,7 +1375,7 @@ public static function replaceGreetingTokens(&$tokenString, $contactDetails = NU if (!empty($greetingTokens) && array_key_exists('contact', $greetingTokens)) { $greetingsReturnProperties = array_flip(CRM_Utils_Array::value('contact', $greetingTokens)); $greetingsReturnProperties = array_fill_keys(array_keys($greetingsReturnProperties), 1); - $contactParams = array('contact_id' => $contactId); + $contactParams = ['contact_id' => $contactId]; $greetingDetails = self::getTokenDetails($contactParams, $greetingsReturnProperties, @@ -1404,13 +1402,13 @@ public static function replaceGreetingTokens(&$tokenString, $contactDetails = NU // Fill the return properties array $greetingTokens = $remainingTokens; reset($greetingTokens); - $greetingsReturnProperties = array(); - while (list($key) = each($greetingTokens)) { - $props = array_flip(CRM_Utils_Array::value($key, $greetingTokens)); + $greetingsReturnProperties = []; + foreach ($greetingTokens as $value) { + $props = array_flip($value); $props = array_fill_keys(array_keys($props), 1); $greetingsReturnProperties = $greetingsReturnProperties + $props; } - $contactParams = array('contact_id' => $contactId); + $contactParams = ['contact_id' => $contactId]; $greetingDetails = self::getTokenDetails($contactParams, $greetingsReturnProperties, FALSE, FALSE, NULL, @@ -1426,25 +1424,75 @@ public static function replaceGreetingTokens(&$tokenString, $contactDetails = NU } } + /** + * At this point, $contactDetails has loaded the contact from the DAO. Any + * (non-custom) missing fields are null. By removing them, we can avoid + * expensive calls to CRM_Contact_BAO_Query. + * + * @param string $tokenString + * @param array $contactDetails + * @param array $greetingTokens + */ + private static function removeNullContactTokens(&$tokenString, $contactDetails, &$greetingTokens) { + + // Only applies to contact tokens + if (!array_key_exists('contact', $greetingTokens)) { + return; + } + + $greetingTokensOriginal = $greetingTokens; + $contactFieldList = CRM_Contact_DAO_Contact::fields(); + // Sometimes contactDetails are in a multidemensional array, sometimes a + // single-dimension array. + if (array_key_exists(0, $contactDetails) && is_array($contactDetails[0])) { + $contactDetails = current($contactDetails[0]); + } + $nullFields = array_keys(array_diff_key($contactFieldList, $contactDetails)); + + // Handle legacy tokens + foreach (self::legacyContactTokens() as $oldToken => $newToken) { + if (CRM_Utils_Array::key($newToken, $nullFields)) { + $nullFields[] = $oldToken; + } + } + + // Remove null contact fields from $greetingTokens + $greetingTokens['contact'] = array_diff($greetingTokens['contact'], $nullFields); + + // Also remove them from $tokenString + $removedTokens = array_diff($greetingTokensOriginal['contact'], $greetingTokens['contact']); + // Handle legacy tokens again, sigh + if (!empty($removedTokens)) { + foreach ($removedTokens as $token) { + if (CRM_Utils_Array::value($token, self::legacyContactTokens()) !== NULL) { + $removedTokens[] = CRM_Utils_Array::value($token, self::legacyContactTokens()); + } + } + foreach ($removedTokens as $token) { + $tokenString = str_replace("{contact.$token}", '', $tokenString); + } + } + } + /** * @param $tokens * * @return array */ public static function flattenTokens(&$tokens) { - $flattenTokens = array(); + $flattenTokens = []; - foreach (array( - 'html', - 'text', - 'subject', - ) as $prop) { + foreach ([ + 'html', + 'text', + 'subject', + ] as $prop) { if (!isset($tokens[$prop])) { continue; } foreach ($tokens[$prop] as $type => $names) { if (!isset($flattenTokens[$type])) { - $flattenTokens[$type] = array(); + $flattenTokens[$type] = []; } foreach ($names as $name) { $flattenTokens[$type][$name] = 1; @@ -1517,7 +1565,8 @@ protected static function _buildContributionTokens() { $key = 'contribution'; if (self::$_tokens[$key] == NULL) { self::$_tokens[$key] = array_keys(array_merge(CRM_Contribute_BAO_Contribution::exportableFields('All'), - array('campaign', 'financial_type') + ['campaign', 'financial_type'], + self::getCustomFieldTokens('Contribution') )); } } @@ -1528,7 +1577,7 @@ protected static function _buildContributionTokens() { protected static function _buildMembershipTokens() { $key = 'membership'; if (!isset(self::$_tokens[$key]) || self::$_tokens[$key] == NULL) { - $membershipTokens = array(); + $membershipTokens = []; $tokens = CRM_Core_SelectValues::membershipTokens(); foreach ($tokens as $token => $dontCare) { $membershipTokens[] = substr($token, (strpos($token, '.') + 1), -1); @@ -1550,13 +1599,13 @@ protected static function _buildMembershipTokens() { * @return string * string with replacements made */ - public static function replaceEntityTokens($entity, $entityArray, $str, $knownTokens = array(), $escapeSmarty = FALSE) { + public static function replaceEntityTokens($entity, $entityArray, $str, $knownTokens = [], $escapeSmarty = FALSE) { if (!$knownTokens || empty($knownTokens[$entity])) { return $str; } $fn = 'get' . ucfirst($entity) . 'TokenReplacement'; - $fn = is_callable(array('CRM_Utils_Token', $fn)) ? $fn : 'getApiTokenReplacement'; + $fn = is_callable(['CRM_Utils_Token', $fn]) ? $fn : 'getApiTokenReplacement'; // since we already know the tokens lets just use them & do str_replace which is faster & simpler than preg_replace foreach ($knownTokens[$entity] as $token) { $replacement = self::$fn($entity, $token, $entityArray); @@ -1576,11 +1625,11 @@ public static function replaceEntityTokens($entity, $entityArray, $str, $knownTo * @return string * @throws \CiviCRM_API3_Exception */ - public static function replaceCaseTokens($caseId, $str, $knownTokens = array(), $escapeSmarty = FALSE) { + public static function replaceCaseTokens($caseId, $str, $knownTokens = [], $escapeSmarty = FALSE) { if (!$knownTokens || empty($knownTokens['case'])) { return $str; } - $case = civicrm_api3('case', 'getsingle', array('id' => $caseId)); + $case = civicrm_api3('case', 'getsingle', ['id' => $caseId]); return self::replaceEntityTokens('case', $case, $str, $knownTokens, $escapeSmarty); } @@ -1597,16 +1646,16 @@ public static function getApiTokenReplacement($entity, $token, $entityArray) { if (!isset($entityArray[$token])) { return ''; } - $field = civicrm_api3($entity, 'getfield', array('action' => 'get', 'name' => $token, 'get_options' => 'get')); + $field = civicrm_api3($entity, 'getfield', ['action' => 'get', 'name' => $token, 'get_options' => 'get']); $field = $field['values']; $fieldType = CRM_Utils_Array::value('type', $field); // Boolean fields if ($fieldType == CRM_Utils_Type::T_BOOLEAN && empty($field['options'])) { - $field['options'] = array(ts('No'), ts('Yes')); + $field['options'] = [ts('No'), ts('Yes')]; } // Match pseudoconstants if (!empty($field['options'])) { - $ret = array(); + $ret = []; foreach ((array) $entityArray[$token] as $val) { $ret[] = $field['options'][$val]; } @@ -1633,7 +1682,8 @@ public static function getApiTokenReplacement($entity, $token, $entityArray) { public static function replaceContributionTokens($str, &$contribution, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) { $key = 'contribution'; if (!$knownTokens || !CRM_Utils_Array::value($key, $knownTokens)) { - return $str; //early return + //early return + return $str; } self::_buildContributionTokens(); @@ -1679,10 +1729,10 @@ public static function replaceMultipleContributionTokens($separator, $str, &$con } if (in_array('receive_date', $knownTokens['contribution'])) { - $formattedDates = array(); + $formattedDates = []; $dates = explode($separator, $contribution['receive_date']); foreach ($dates as $date) { - $formattedDates[] = CRM_Utils_Date::customFormat($date, NULL, array('j', 'm', 'Y')); + $formattedDates[] = CRM_Utils_Date::customFormat($date, NULL, ['j', 'm', 'Y']); } $str = str_replace("{contribution.receive_date}", implode($separator, $formattedDates), $str); unset($knownTokens['contribution']['receive_date']); @@ -1715,10 +1765,11 @@ public static function getMembershipTokenReplacement($entity, $token, $membershi case 'fee': try { - $value = civicrm_api3('membership_type', 'getvalue', array( - 'id' => $membership['membership_type_id'], - 'return' => 'minimum_fee', - )); + $value = civicrm_api3('membership_type', 'getvalue', [ + 'id' => $membership['membership_type_id'], + 'return' => 'minimum_fee', + ]); + $value = CRM_Utils_Money::format($value, NULL, NULL, TRUE); } catch (CiviCRM_API3_Exception $e) { // we can anticipate we will get an error if the minimum fee is set to 'NULL' because of the way the @@ -1765,7 +1816,7 @@ public static function getContributionTokenReplacement($token, &$contribution, $ case 'receive_date': $value = CRM_Utils_Array::retrieveValueRecursive($contribution, $token); - $value = CRM_Utils_Date::customFormat($value, NULL, array('j', 'm', 'Y')); + $value = CRM_Utils_Date::customFormat($value, NULL, ['j', 'm', 'Y']); break; default: @@ -1789,12 +1840,31 @@ public static function getContributionTokenReplacement($token, &$contribution, $ * [legacy_token => new_token] */ public static function legacyContactTokens() { - return array( + return [ 'individual_prefix' => 'prefix_id', 'individual_suffix' => 'suffix_id', 'gender' => 'gender_id', 'communication_style' => 'communication_style_id', - ); + ]; + } + + /** + * Get all custom field tokens of $entity + * + * @param string $entity + * @param bool $usedForTokenWidget + * + * @return array + * return custom field tokens in array('custom_N' => 'label') format + */ + public static function getCustomFieldTokens($entity, $usedForTokenWidget = FALSE) { + $customTokens = []; + $tokenName = $usedForTokenWidget ? "{contribution.custom_%d}" : "custom_%d"; + foreach (CRM_Core_BAO_CustomField::getFields($entity) as $id => $info) { + $customTokens[sprintf($tokenName, $id)] = $info['label']; + } + + return $customTokens; } /** @@ -1804,7 +1874,7 @@ public static function legacyContactTokens() { * @return array */ public static function formatTokensForDisplay($tokens) { - $sorted = $output = array(); + $sorted = $output = []; // Sort in ascending order by ignoring word case natcasesort($tokens); @@ -1815,7 +1885,7 @@ public static function formatTokensForDisplay($tokens) { // Check to see if this token is already in a group e.g. for custom fields $split = explode(' :: ', $v); if (!empty($split[1])) { - $sorted[$split[1]][] = array('id' => $k, 'text' => $split[0]); + $sorted[$split[1]][] = ['id' => $k, 'text' => $split[0]]; } // Group by entity else { @@ -1826,13 +1896,13 @@ public static function formatTokensForDisplay($tokens) { else { $entity = 'Contact'; } - $sorted[ts($entity)][] = array('id' => $k, 'text' => $v); + $sorted[ts($entity)][] = ['id' => $k, 'text' => $v]; } } ksort($sorted); foreach ($sorted as $k => $v) { - $output[] = array('text' => $k, 'children' => $v); + $output[] = ['text' => $k, 'children' => $v]; } return $output; diff --git a/CRM/Utils/Type.php b/CRM/Utils/Type.php index 43b920f8be4b..483e5629b176 100644 --- a/CRM/Utils/Type.php +++ b/CRM/Utils/Type.php @@ -1,9 +1,9 @@ 'int representing type' + */ + public static function getValidTypes() { + return [ + 'Int' => self::T_INT, + 'String' => self::T_STRING, + 'Enum' => self::T_ENUM, + 'Date' => self::T_DATE, + 'Time' => self::T_TIME, + 'Boolean' => self::T_BOOLEAN, + 'Text' => self::T_TEXT, + 'Blob' => self::T_BLOB, + 'Timestamp' => self::T_TIMESTAMP, + 'Float' => self::T_FLOAT, + 'Money' => self::T_MONEY, + 'Email' => self::T_EMAIL, + 'Mediumblob' => self::T_MEDIUMBLOB, + ]; + } + /** * Get the data_type for the field. * @@ -201,19 +233,22 @@ public static function validateAll($data, $type, $abort = TRUE) { * * @return mixed * The data, escaped if necessary. + * @throws \Exception */ public static function escape($data, $type, $abort = TRUE) { switch ($type) { case 'Integer': case 'Int': - if (CRM_Utils_Rule::integer($data)) { - return (int) $data; - } - break; - case 'Positive': - if (CRM_Utils_Rule::positiveInteger($data)) { - return (int) $data; + case 'Float': + case 'Money': + case 'Date': + case 'Timestamp': + case 'ContactReference': + case 'MysqlOrderByDirection': + $validatedData = self::validate($data, $type, $abort); + if (isset($validatedData)) { + return $validatedData; } break; @@ -255,43 +290,10 @@ public static function escape($data, $type, $abort = TRUE) { } break; - case 'Float': - case 'Money': - if (CRM_Utils_Rule::numeric($data)) { - return $data; - } - break; - case 'String': case 'Memo': case 'Text': - return CRM_Core_DAO::escapeString($data); - - case 'Date': - case 'Timestamp': - // a null date or timestamp is valid - if (strlen(trim($data)) == 0) { - return trim($data); - } - - if ((preg_match('/^\d{8}$/', $data) || - preg_match('/^\d{14}$/', $data) - ) && - CRM_Utils_Rule::mysqlDate($data) - ) { - return $data; - } - break; - - case 'ContactReference': - if (strlen(trim($data)) == 0) { - return trim($data); - } - - if (CRM_Utils_Rule::validContact($data)) { - return (int) $data; - } - break; + return CRM_Core_DAO::escapeString(self::validate($data, $type, $abort)); case 'MysqlColumnNameOrAlias': if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) { @@ -303,12 +305,6 @@ public static function escape($data, $type, $abort = TRUE) { } break; - case 'MysqlOrderByDirection': - if (CRM_Utils_Rule::mysqlOrderByDirection($data)) { - return strtolower($data); - } - break; - case 'MysqlOrderBy': if (CRM_Utils_Rule::mysqlOrderBy($data)) { $parts = explode(',', $data); @@ -339,7 +335,7 @@ public static function escape($data, $type, $abort = TRUE) { } // Normal clause. - $part = preg_replace_callback('/^(?:(?:((?:`[\w-]{1,64}`|[\w-]{1,64}))(?:\.))?(`[\w-]{1,64}`|[\w-]{1,64})(?: (asc|desc))?)$/i', array('CRM_Utils_Type', 'mysqlOrderByCallback'), trim($part)); + $part = preg_replace_callback('/^(?:(?:((?:`[\w-]{1,64}`|[\w-]{1,64}))(?:\.))?(`[\w-]{1,64}`|[\w-]{1,64})(?: (asc|desc))?)$/i', ['CRM_Utils_Type', 'mysqlOrderByCallback'], trim($part)); } return implode(', ', $parts); } @@ -355,6 +351,7 @@ public static function escape($data, $type, $abort = TRUE) { // @todo Use exceptions instead of CRM_Core_Error::fatal(). if ($abort) { $data = htmlentities($data); + CRM_Core_Error::fatal("$data is not of the type $type"); } return NULL; @@ -369,13 +366,47 @@ public static function escape($data, $type, $abort = TRUE) { * The type to validate against. * @param bool $abort * If TRUE, the operation will CRM_Core_Error::fatal() on invalid data. - * @name string $name + * @param string $name * The name of the attribute + * @param bool $isThrowException + * Should an exception be thrown rather than a using a deprecated fatal error. * * @return mixed * The data, escaped if necessary + * + * @throws \CRM_Core_Exception */ - public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ') { + public static function validate($data, $type, $abort = TRUE, $name = 'One of parameters ', $isThrowException = FALSE) { + + $possibleTypes = [ + 'Integer', + 'Int', + 'Positive', + 'CommaSeparatedIntegers', + 'Boolean', + 'Float', + 'Money', + 'Text', + 'String', + 'Link', + 'Memo', + 'Date', + 'Timestamp', + 'ContactReference', + 'MysqlColumnNameOrAlias', + 'MysqlOrderByDirection', + 'MysqlOrderBy', + 'ExtensionKey', + 'Json', + 'Alphanumeric', + 'Color', + ]; + if (!in_array($type, $possibleTypes)) { + if ($isThrowException) { + throw new CRM_Core_Exception(ts('Invalid type, must be one of : ' . implode($possibleTypes))); + } + CRM_Core_Error::fatal(ts('Invalid type, must be one of : ' . implode($possibleTypes))); + } switch ($type) { case 'Integer': case 'Int': @@ -390,12 +421,6 @@ public static function validate($data, $type, $abort = TRUE, $name = 'One of par } break; - case 'Boolean': - if (CRM_Utils_Rule::boolean($data)) { - return $data; - } - break; - case 'Float': case 'Money': if (CRM_Utils_Rule::numeric($data)) { @@ -410,18 +435,6 @@ public static function validate($data, $type, $abort = TRUE, $name = 'One of par return $data; case 'Date': - // a null date is valid - if (strlen(trim($data)) == 0) { - return trim($data); - } - - if (preg_match('/^\d{8}$/', $data) && - CRM_Utils_Rule::mysqlDate($data) - ) { - return $data; - } - break; - case 'Timestamp': // a null timestamp is valid if (strlen(trim($data)) == 0) { @@ -444,13 +457,7 @@ public static function validate($data, $type, $abort = TRUE, $name = 'One of par } if (CRM_Utils_Rule::validContact($data)) { - return $data; - } - break; - - case 'MysqlColumnNameOrAlias': - if (CRM_Utils_Rule::mysqlColumnNameOrAlias($data)) { - return $data; + return (int) $data; } break; @@ -460,19 +467,24 @@ public static function validate($data, $type, $abort = TRUE, $name = 'One of par } break; - case 'MysqlOrderBy': - if (CRM_Utils_Rule::mysqlOrderBy($data)) { + case 'ExtensionKey': + if (CRM_Utils_Rule::checkExtensionKeyIsValid($data)) { return $data; } break; default: - CRM_Core_Error::fatal("Cannot recognize $type for $data"); - break; + $check = lcfirst($type); + if (CRM_Utils_Rule::$check($data)) { + return $data; + } } if ($abort) { $data = htmlentities($data); + if ($isThrowException) { + throw new CRM_Core_Exception("$name (value: $data) is not of the type $type"); + } CRM_Core_Error::fatal("$name (value: $data) is not of the type $type"); } @@ -518,12 +530,12 @@ public static function mysqlOrderByCallback($matches) { } /** - * Get list of avaliable Data Tupes for Option Groups + * Get list of avaliable Data Types for Option Groups * * @return array */ public static function dataTypes() { - $types = array( + $types = [ 'Integer', 'String', 'Date', @@ -531,7 +543,7 @@ public static function dataTypes() { 'Timestamp', 'Money', 'Email', - ); + ]; return array_combine($types, $types); } diff --git a/CRM/Utils/Url.php b/CRM/Utils/Url.php new file mode 100644 index 000000000000..4396263aacef --- /dev/null +++ b/CRM/Utils/Url.php @@ -0,0 +1,55 @@ +__toString(); + } + +} diff --git a/CRM/Utils/Verp.php b/CRM/Utils/Verp.php index 0ecf6050c2ab..a9d5140c3cdd 100644 --- a/CRM/Utils/Verp.php +++ b/CRM/Utils/Verp.php @@ -1,9 +1,9 @@ '2B', '@' => '40', ':' => '3A', @@ -44,11 +46,13 @@ class CRM_Utils_Verp { '-' => '2D', '[' => '5B', ']' => '5D', - ); + ]; - /* Mapping of hex codes to reserved characters */ - - static $decodeMap = array( + /** + * Mapping of hex codes to reserved characters + * @var array + */ + public static $decodeMap = [ '40' => '@', '3A' => ':', '25' => '%', @@ -57,7 +61,7 @@ class CRM_Utils_Verp { '5B' => '[', '5D' => ']', '2B' => '+', - ); + ]; /** * Encode the sender's address with the VERPed recipient. @@ -109,7 +113,7 @@ public static function &verpdecode($address) { $rdomain = preg_replace("/+$code/i", $char, $rdomain); } - return array("$slocal@$sdomain", "$rlocal@$rdomain"); + return ["$slocal@$sdomain", "$rlocal@$rdomain"]; } } diff --git a/CRM/Utils/VersionCheck.php b/CRM/Utils/VersionCheck.php index 6cd1507bd69c..9d2fe9f6f2c8 100644 --- a/CRM/Utils/VersionCheck.php +++ b/CRM/Utils/VersionCheck.php @@ -1,9 +1,9 @@ localVersion = CRM_Utils_System::version(); - $this->localMajorVersion = $this->getMajorVersion($this->localVersion); $this->cacheFile = CRM_Core_Config::singleton()->uploadDir . self::CACHEFILE_NAME; } @@ -106,7 +98,7 @@ public function initialize() { // Populate remote $versionInfo from cache file $this->isInfoAvailable = $this->readCacheFile(); - // Poor-man's cron fallback if scheduled job is enabled but has failed to run + // Fallback if scheduled job is enabled but has failed to run. $expiryTime = time() - self::CACHEFILE_EXPIRE; if (!empty($this->cronJob['is_active']) && (!$this->isInfoAvailable || filemtime($this->cacheFile) < $expiryTime) @@ -126,113 +118,19 @@ public function initialize() { * * @param $info */ - public function setVersionInfo($info) { - $this->versionInfo = (array) $info; - // Sort version info in ascending order for easier comparisons - ksort($this->versionInfo, SORT_NUMERIC); - } - - /** - * Finds the release info for a minor version. - * @param string $version - * @return array|null - */ - public function getReleaseInfo($version) { - $majorVersion = $this->getMajorVersion($version); - if (isset($this->versionInfo[$majorVersion])) { - foreach ($this->versionInfo[$majorVersion]['releases'] as $info) { - if ($info['version'] == $version) { - return $info; - } - } - } - return NULL; - } - - /** - * @param $minorVersion - * @return string - */ - public function getMajorVersion($minorVersion) { - if (!$minorVersion) { - return NULL; - } - list($a, $b) = explode('.', $minorVersion); - return "$a.$b"; + protected function setVersionInfo($info) { + $this->versionInfo = $info; } - /** - * Get the latest version number if it's newer than the local one - * - * @return array - * Returns version number of the latest release if it is greater than the local version, - * along with the type of upgrade (regular/security) needed and the status of the major - * version + * @return array|NULL + * message: string + * title: string + * severity: string + * Ex: 'info', 'notice', 'warning', 'critical'. */ - public function isNewerVersionAvailable() { - $return = array( - 'version' => NULL, - 'upgrade' => NULL, - 'status' => NULL, - ); - - if ($this->versionInfo && $this->localVersion) { - if (isset($this->versionInfo[$this->localMajorVersion])) { - switch (CRM_Utils_Array::value('status', $this->versionInfo[$this->localMajorVersion])) { - case 'stable': - case 'lts': - case 'testing': - // look for latest version in this major version - $releases = $this->checkBranchForNewVersion($this->versionInfo[$this->localMajorVersion]); - if ($releases['newest']) { - $return['version'] = $releases['newest']; - - // check for intervening security releases - $return['upgrade'] = ($releases['security']) ? 'security' : 'regular'; - } - break; - - case 'eol': - default: - // look for latest version ever - foreach ($this->versionInfo as $majorVersionNumber => $majorVersion) { - if ($majorVersionNumber < $this->localMajorVersion || $majorVersion['status'] == 'testing') { - continue; - } - $releases = $this->checkBranchForNewVersion($this->versionInfo[$majorVersionNumber]); - - if ($releases['newest']) { - $return['version'] = $releases['newest']; - - // check for intervening security releases - $return['upgrade'] = ($releases['security'] || $return['upgrade'] == 'security') ? 'security' : 'regular'; - } - } - } - $return['status'] = $this->versionInfo[$this->localMajorVersion]['status']; - } - else { - // Figure if the version is really old or really new - $wayOld = TRUE; - - foreach ($this->versionInfo as $majorVersionNumber => $majorVersion) { - $wayOld = ($this->localMajorVersion < $majorVersionNumber); - } - - if ($wayOld) { - $releases = $this->checkBranchForNewVersion($majorVersion); - - $return = array( - 'version' => $releases['newest'], - 'upgrade' => 'security', - 'status' => 'eol', - ); - } - } - } - - return $return; + public function getVersionMessages() { + return $this->isInfoAvailable ? $this->versionInfo : NULL; } /** @@ -243,28 +141,6 @@ public function fetch() { $this->pingBack(); } - /** - * @param $majorVersion - * @return null|string - */ - private function checkBranchForNewVersion($majorVersion) { - $newerVersion = array( - 'newest' => NULL, - 'security' => NULL, - ); - if (!empty($majorVersion['releases'])) { - foreach ($majorVersion['releases'] as $release) { - if (version_compare($this->localVersion, $release['version']) < 0) { - $newerVersion['newest'] = $release['version']; - if (CRM_Utils_Array::value('security', $release)) { - $newerVersion['security'] = $release['version']; - } - } - } - } - return $newerVersion; - } - /** * Collect info about the site to be sent as pingback data. */ @@ -273,20 +149,20 @@ private function getSiteStats() { $siteKey = md5(defined('CIVICRM_SITE_KEY') ? CIVICRM_SITE_KEY : ''); // Calorie-free pingback for alphas - $this->stats = array('version' => $this->localVersion); + $this->stats = ['version' => $this->localVersion]; // Non-alpha versions get the full treatment if ($this->localVersion && !strpos($this->localVersion, 'alpha')) { - $this->stats += array( + $this->stats += [ 'hash' => md5($siteKey . $config->userFrameworkBaseURL), 'uf' => $config->userFramework, 'lang' => $config->lcMessages, 'co' => $config->defaultContactCountry, 'ufv' => $config->userSystem->getVersion(), 'PHP' => phpversion(), - 'MySQL' => CRM_CORE_DAO::singleValueQuery('SELECT VERSION()'), + 'MySQL' => CRM_Core_DAO::singleValueQuery('SELECT VERSION()'), 'communityMessagesUrl' => Civi::settings()->get('communityMessagesUrl'), - ); + ]; $this->getDomainStats(); $this->getPayProcStats(); $this->getEntityStats(); @@ -301,13 +177,12 @@ private function getPayProcStats() { $dao = new CRM_Financial_DAO_PaymentProcessor(); $dao->is_active = 1; $dao->find(); - $ppTypes = array(); - - // Get title and id for all processor types - $ppTypeNames = CRM_Core_PseudoConstant::paymentProcessorType(); + $ppTypes = []; + // Get title for all processor types + // FIXME: This should probably be getName, but it has always returned translated label so we stick with that for now as it would affect stats while ($dao->fetch()) { - $ppTypes[] = $ppTypeNames[$dao->payment_processor_type_id]; + $ppTypes[] = CRM_Core_PseudoConstant::getLabel('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', $dao->payment_processor_type_id); } // add the .-separated list of the processor types $this->stats['PPTypes'] = implode(',', array_unique($ppTypes)); @@ -318,7 +193,7 @@ private function getPayProcStats() { * Add info to the 'entities' array */ private function getEntityStats() { - $tables = array( + $tables = [ 'CRM_Activity_DAO_Activity' => 'is_test = 0', 'CRM_Case_DAO_Case' => 'is_deleted = 0', 'CRM_Contact_DAO_Contact' => 'is_deleted = 0', @@ -341,17 +216,17 @@ private function getEntityStats() { 'CRM_Pledge_DAO_Pledge' => 'is_test = 0', 'CRM_Pledge_DAO_PledgeBlock' => NULL, 'CRM_Mailing_Event_DAO_Delivered' => NULL, - ); + ]; foreach ($tables as $daoName => $where) { $dao = new $daoName(); if ($where) { $dao->whereAdd($where); } $short_name = substr($daoName, strrpos($daoName, '_') + 1); - $this->stats['entities'][] = array( + $this->stats['entities'][] = [ 'name' => $short_name, 'size' => $dao->count(), - ); + ]; } } @@ -363,11 +238,11 @@ private function getExtensionStats() { // Core components $config = CRM_Core_Config::singleton(); foreach ($config->enableComponents as $comp) { - $this->stats['extensions'][] = array( + $this->stats['extensions'][] = [ 'name' => 'org.civicrm.component.' . strtolower($comp), 'enabled' => 1, 'version' => $this->stats['version'], - ); + ]; } // Contrib extensions $mapper = CRM_Extension_System::singleton()->getMapper(); @@ -375,11 +250,11 @@ private function getExtensionStats() { $dao->find(); while ($dao->fetch()) { $info = $mapper->keyToInfo($dao->full_name); - $this->stats['extensions'][] = array( + $this->stats['extensions'][] = [ 'name' => $dao->full_name, 'enabled' => $dao->is_active, 'version' => isset($info->version) ? $info->version : NULL, - ); + ]; } } @@ -390,21 +265,21 @@ private function getDomainStats() { // Start with default value NULL, then check to see if there's a better // value to be had. $this->stats['domain_isoCode'] = NULL; - $params = array( + $params = [ 'id' => CRM_Core_Config::domainID(), - ); + ]; $domain_result = civicrm_api3('domain', 'getsingle', $params); if (!empty($domain_result['contact_id'])) { - $address_params = array( + $address_params = [ 'contact_id' => $domain_result['contact_id'], 'is_primary' => 1, 'sequential' => 1, - ); + ]; $address_result = civicrm_api3('address', 'get', $address_params); if ($address_result['count'] == 1 && !empty($address_result['values'][0]['country_id'])) { - $country_params = array( + $country_params = [ 'id' => $address_result['values'][0]['country_id'], - ); + ]; $country_result = civicrm_api3('country', 'getsingle', $country_params); if (!empty($country_result['iso_code'])) { $this->stats['domain_isoCode'] = $country_result['iso_code']; @@ -418,13 +293,13 @@ private function getDomainStats() { * Store results in the cache file */ private function pingBack() { - $params = array( - 'http' => array( + $params = [ + 'http' => [ 'method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query($this->stats), - ), - ); + ], + ]; $ctx = stream_context_create($params); $rawJson = file_get_contents($this->pingbackUrl, FALSE, $ctx); $versionInfo = $rawJson ? json_decode($rawJson, TRUE) : NULL; @@ -460,16 +335,25 @@ private function writeCacheFile($contents) { } } + /** + * Removes cached version info. + */ + public function flushCache() { + if (file_exists($this->cacheFile)) { + unlink($this->cacheFile); + } + } + /** * Lookup version_check scheduled job */ private function getJob() { - $jobs = civicrm_api3('Job', 'get', array( + $jobs = civicrm_api3('Job', 'get', [ 'sequential' => 1, 'api_action' => "version_check", 'api_entity' => "job", - )); - $this->cronJob = CRM_Utils_Array::value(0, $jobs['values'], array()); + ]); + $this->cronJob = CRM_Utils_Array::value(0, $jobs['values'], []); } } diff --git a/CRM/Utils/VisualBundle.php b/CRM/Utils/VisualBundle.php new file mode 100644 index 000000000000..139a7ec19e8a --- /dev/null +++ b/CRM/Utils/VisualBundle.php @@ -0,0 +1,110 @@ +addScriptUrl(Civi::service('asset_manager')->getUrl('visual-bundle.js')); + Civi::resources()->addStyleUrl(Civi::service('asset_manager')->getUrl('visual-bundle.css')); + } + + /** + * Generate asset content (when accessed via AssetBuilder). + * + * @param \Civi\Core\Event\GenericHookEvent $event + * @see CRM_Utils_hook::buildAsset() + * @see \Civi\Core\AssetBuilder + */ + public static function buildAssetJs($event) { + if ($event->asset !== 'visual-bundle.js') { + return; + } + + $files = [ + 'crossfilter' => '[civicrm.bower]/crossfilter-1.3.x/crossfilter.min.js', + 'd3' => '[civicrm.bower]/d3-3.5.x/d3.min.js', + 'dc' => '[civicrm.bower]/dc-2.1.x/dc.min.js', + ]; + + $content = []; + $content[] = "(function(){"; + $content[] = "var backups = {d3: window.d3, crossfilter: window.crossfilter, dc: window.dc}"; + $content[] = 'window.CRM = window.CRM || {};'; + $content[] = 'CRM.visual = CRM.visual || {};'; + foreach ($files as $var => $file) { + $content[] = "// File: $file"; + $content[] = file_get_contents(Civi::paths()->getPath($file)); + } + foreach ($files as $var => $file) { + $content[] = "CRM.visual.$var = $var;"; + } + foreach ($files as $var => $file) { + $content[] = "window.$var = backups.$var;"; + } + $content[] = "})();"; + + $event->mimeType = 'application/javascript'; + $event->content = implode("\n", $content); + } + + /** + * Generate asset content (when accessed via AssetBuilder). + * + * @param \Civi\Core\Event\GenericHookEvent $event + * @see CRM_Utils_hook::buildAsset() + * @see \Civi\Core\AssetBuilder + */ + public static function buildAssetCss($event) { + if ($event->asset !== 'visual-bundle.css') { + return; + } + + $files = [ + '[civicrm.bower]/dc-2.1.x/dc.min.css', + ]; + + $content = []; + foreach ($files as $file) { + $content[] = "// File: $file"; + $content[] = file_get_contents(Civi::paths()->getPath($file)); + } + + $event->mimeType = 'text/css'; + $event->content = implode("\n", $content); + } + +} diff --git a/CRM/Utils/Weight.php b/CRM/Utils/Weight.php index d514351845db..3e4add68b7cf 100644 --- a/CRM/Utils/Weight.php +++ b/CRM/Utils/Weight.php @@ -1,9 +1,9 @@ fetch()) { if (in_array($field->weight, $weights)) { $sameWeightCount++; @@ -308,11 +309,11 @@ public static function &query( $fields = &$dao->fields(); $fieldlist = array_keys($fields); - $whereConditions = array(); + $whereConditions = []; if ($additionalWhere) { $whereConditions[] = $additionalWhere; } - $params = array(); + $params = []; $fieldNum = 0; if (is_array($fieldValues)) { foreach ($fieldValues as $fieldName => $value) { @@ -323,7 +324,7 @@ public static function &query( $fieldNum++; $whereConditions[] = "$fieldName = %$fieldNum"; $fieldType = $fields[$fieldName]['type']; - $params[$fieldNum] = array($value, CRM_Utils_Type::typeToString($fieldType)); + $params[$fieldNum] = [$value, CRM_Utils_Type::typeToString($fieldType)]; } } $where = implode(' AND ', $whereConditions); @@ -386,13 +387,13 @@ public static function addOrder(&$rows, $daoName, $idName, $returnURL, $filter = $config = CRM_Core_Config::singleton(); $imageURL = $config->userFrameworkResourceURL . 'i/arrow'; - $queryParams = array( + $queryParams = [ 'reset' => 1, 'dao' => $daoName, 'idName' => $idName, 'url' => $returnURL, 'filter' => $filter, - ); + ]; $signer = new CRM_Utils_Signer(CRM_Core_Key::privateKey(), self::$SIGNABLE_FIELDS); $queryParams['_sgn'] = $signer->sign($queryParams); @@ -403,7 +404,7 @@ public static function addOrder(&$rows, $daoName, $idName, $returnURL, $filter = $prevID = $ids[$i - 1]; $nextID = $ids[$i + 1]; - $links = array(); + $links = []; $url = "{$baseURL}&src=$id"; if ($prevID != 0) { @@ -461,17 +462,17 @@ public static function fixOrder() { $tableName = $object->tableName(); $query = "UPDATE $tableName SET weight = %1 WHERE $idName = %2"; - $params = array( - 1 => array($dstWeight, 'Integer'), - 2 => array($src, 'Integer'), - ); + $params = [ + 1 => [$dstWeight, 'Integer'], + 2 => [$src, 'Integer'], + ]; CRM_Core_DAO::executeQuery($query, $params); if ($dir == 'swap') { - $params = array( - 1 => array($srcWeight, 'Integer'), - 2 => array($dst, 'Integer'), - ); + $params = [ + 1 => [$srcWeight, 'Integer'], + 2 => [$dst, 'Integer'], + ]; CRM_Core_DAO::executeQuery($query, $params); } elseif ($dir == 'first') { @@ -480,10 +481,10 @@ public static function fixOrder() { if ($filter) { $query .= " AND $filter"; } - $params = array( - 1 => array($src, 'Integer'), - 2 => array($srcWeight, 'Integer'), - ); + $params = [ + 1 => [$src, 'Integer'], + 2 => [$srcWeight, 'Integer'], + ]; CRM_Core_DAO::executeQuery($query, $params); } elseif ($dir == 'last') { @@ -492,10 +493,10 @@ public static function fixOrder() { if ($filter) { $query .= " AND $filter"; } - $params = array( - 1 => array($src, 'Integer'), - 2 => array($srcWeight, 'Integer'), - ); + $params = [ + 1 => [$src, 'Integer'], + 2 => [$srcWeight, 'Integer'], + ]; CRM_Core_DAO::executeQuery($query, $params); } @@ -510,9 +511,9 @@ public static function fixOrderOutput($url) { CRM_Utils_System::redirect($url); } - CRM_Core_Page_AJAX::returnJsonResponse(array( + CRM_Core_Page_AJAX::returnJsonResponse([ 'userContext' => $url, - )); + ]); } } diff --git a/CRM/Utils/Wrapper.php b/CRM/Utils/Wrapper.php index 2716dd1f6780..df9b8a8f32f8 100644 --- a/CRM/Utils/Wrapper.php +++ b/CRM/Utils/Wrapper.php @@ -1,9 +1,9 @@ SimpleXMLElement|FALSE, 1 => errorMessage|FALSE) */ public static function parseFile($file) { - $xml = FALSE; // SimpleXMLElement - $error = FALSE; // string + // SimpleXMLElement + $xml = FALSE; + // string + $error = FALSE; if (!file_exists($file)) { $error = 'File ' . $file . ' does not exist.'; @@ -60,7 +62,7 @@ public static function parseFile($file) { libxml_use_internal_errors($oldLibXMLErrors); } - return array($xml, $error); + return [$xml, $error]; } /** @@ -72,8 +74,10 @@ public static function parseFile($file) { * (0 => SimpleXMLElement|FALSE, 1 => errorMessage|FALSE) */ public static function parseString($string) { - $xml = FALSE; // SimpleXMLElement - $error = FALSE; // string + // SimpleXMLElement + $xml = FALSE; + // string + $error = FALSE; $oldLibXMLErrors = libxml_use_internal_errors(); libxml_use_internal_errors(TRUE); @@ -87,7 +91,7 @@ public static function parseString($string) { libxml_use_internal_errors($oldLibXMLErrors); - return array($xml, $error); + return [$xml, $error]; } /** @@ -96,14 +100,14 @@ public static function parseString($string) { * @return string */ protected static function formatErrors($errors) { - $messages = array(); + $messages = []; foreach ($errors as $error) { if ($error->level != LIBXML_ERR_ERROR && $error->level != LIBXML_ERR_FATAL) { continue; } - $parts = array(); + $parts = []; if ($error->file) { $parts[] = "File=$error->file"; } @@ -126,7 +130,7 @@ protected static function formatErrors($errors) { * @return array */ public static function xmlObjToArray($obj) { - $arr = array(); + $arr = []; if (is_object($obj)) { $obj = get_object_vars($obj); } diff --git a/CRM/Utils/Zip.php b/CRM/Utils/Zip.php index e901e0b690e8..2ef4c7915d00 100644 --- a/CRM/Utils/Zip.php +++ b/CRM/Utils/Zip.php @@ -1,9 +1,9 @@ numFiles; $base = FALSE; @@ -77,9 +77,9 @@ static public function findBaseDirName(ZipArchive $zip) { * @return array(string) * no trailing / */ - static public function findBaseDirs(ZipArchive $zip) { + public static function findBaseDirs(ZipArchive $zip) { $cnt = $zip->numFiles; - $basedirs = array(); + $basedirs = []; for ($i = 0; $i < $cnt; $i++) { $filename = $zip->getNameIndex($i); @@ -101,7 +101,7 @@ static public function findBaseDirs(ZipArchive $zip) { * @return string|bool * Return string or FALSE */ - static public function guessBasedir(ZipArchive $zip, $expected) { + public static function guessBasedir(ZipArchive $zip, $expected) { $candidate = FALSE; $basedirs = CRM_Utils_Zip::findBaseDirs($zip); if (in_array($expected, $basedirs)) { @@ -118,7 +118,6 @@ static public function guessBasedir(ZipArchive $zip, $expected) { } } - /** * An inefficient helper for creating a ZIP file from data in memory. * This is only intended for building temp files for unit-testing. @@ -131,7 +130,7 @@ static public function guessBasedir(ZipArchive $zip, $expected) { * Array, keys are file names and values are file contents. * @return bool */ - static public function createTestZip($zipName, $dirs, $files) { + public static function createTestZip($zipName, $dirs, $files) { $zip = new ZipArchive(); $res = $zip->open($zipName, ZipArchive::CREATE); if ($res === TRUE) { diff --git a/CRM/Widget/Widget.php b/CRM/Widget/Widget.php index 29586c3658f1..dbc4afe80cce 100644 --- a/CRM/Widget/Widget.php +++ b/CRM/Widget/Widget.php @@ -11,29 +11,29 @@ */ class CRM_Widget_Widget { - static $_methodTable; + public static $_methodTable; public function initialize() { if (!self::$_methodTable) { - self::$_methodTable = array( - 'getContributionPageData' => array( + self::$_methodTable = [ + 'getContributionPageData' => [ 'description' => 'Gets all campaign related data and returns it as a std class.', 'access' => 'remote', - 'arguments' => array( + 'arguments' => [ 'contributionPageID', 'widgetID', - ), - ), - 'getEmbedCode' => array( + ], + ], + 'getEmbedCode' => [ 'description' => 'Gets embed code. Perhaps overkill, but we can track dropoffs in this case. by # of people requesting embed code / number of unique instances.', 'access' => 'remote', - 'arguments' => array( + 'arguments' => [ 'contributionPageID', 'widgetID', 'format', - ), - ), - ); + ], + ], + ]; } } @@ -109,7 +109,7 @@ public function getContributionPageData($contributionPageID, $widgetID) { WHERE is_test = 0 AND contribution_status_id = 1 AND contribution_page_id = %1"; - $params = array(1 => array($contributionPageID, 'Integer')); + $params = [1 => [$contributionPageID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); if ($dao->fetch()) { $data->num_donors = $dao->count; @@ -123,7 +123,7 @@ public function getContributionPageData($contributionPageID, $widgetID) { SELECT goal_amount, start_date, end_date, is_active FROM civicrm_contribution_page WHERE id = %1"; - $params = array(1 => array($contributionPageID, 'Integer')); + $params = [1 => [$contributionPageID, 'Integer']]; $dao = CRM_Core_DAO::executeQuery($query, $params); if ($dao->fetch()) { $data->money_target = $dao->goal_amount; @@ -158,7 +158,7 @@ public function getContributionPageData($contributionPageID, $widgetID) { $data->homepage_link = $widget->url_homepage; // movie clip colors, must be in '0xRRGGBB' format - $data->colors = array(); + $data->colors = []; $hexPrefix = '0x'; $data->colors["title"] = str_replace('#', $hexPrefix, $widget->color_title); diff --git a/Civi.php b/Civi.php index ae5dc6f4b1dd..12b2119b45bc 100644 --- a/Civi.php +++ b/Civi.php @@ -18,7 +18,7 @@ class Civi { /** * A central location for static variable storage. - * + * @var array * @code * `Civi::$statics[__CLASS__]['foo'] = 'bar'; * @endcode @@ -26,20 +26,24 @@ class Civi { public static $statics = array(); /** - * EXPERIMENTAL. Retrieve a named cache instance. - * - * This interface is flagged as experimental due to political - * ambiguity in PHP community -- PHP-FIG has an open but - * somewhat controversial draft standard for caching. Based on - * the current draft, it's expected that this function could - * simultaneously support both CRM_Utils_Cache_Interface and - * PSR-6, but that depends on whether PSR-6 changes any more. + * Retrieve a named cache instance. * * @param string $name * The name of the cache. The 'default' cache is biased toward * high-performance caches (eg memcache/redis/apc) when * available and falls back to single-request (static) caching. + * Ex: 'short' or 'default' is useful for high-speed, short-lived cache data. + * This is appropriate if you believe that latency (millisecond-level + * read time) is the main factor. For example: caching data from + * a couple SQL queries. + * Ex: 'long' can be useful for longer-lived cache data. It's appropriate if + * you believe that longevity (e.g. surviving for several hours or a day) + * is more important than millisecond-level access time. For example: + * caching the result of a simple metadata-query. + * * @return CRM_Utils_Cache_Interface + * NOTE: Beginning in CiviCRM v5.4, the cache instance complies with + * PSR-16 (\Psr\SimpleCache\CacheInterface). */ public static function cache($name = 'default') { return \Civi\Core\Container::singleton()->get('cache.' . $name); @@ -113,6 +117,22 @@ public static function resources() { return CRM_Core_Resources::singleton(); } + /** + * Obtain the contact's personal settings. + * + * @param NULL|int $contactID + * For the default/active user's contact, leave $domainID as NULL. + * @param NULL|int $domainID + * For the default domain, leave $domainID as NULL. + * @return \Civi\Core\SettingsBag + * @throws CRM_Core_Exception + * If there is no contact, then there's no SettingsBag, and we'll throw + * an exception. + */ + public static function contactSettings($contactID = NULL, $domainID = NULL) { + return \Civi\Core\Container::getBootService('settings_manager')->getBagByContact($domainID, $contactID); + } + /** * Obtain the domain settings. * diff --git a/Civi/API/Api3SelectQuery.php b/Civi/API/Api3SelectQuery.php index 1585b152b12d..d036484d2a24 100644 --- a/Civi/API/Api3SelectQuery.php +++ b/Civi/API/Api3SelectQuery.php @@ -1,9 +1,9 @@ where as $key => $value) { $table_name = NULL; $column_name = NULL; @@ -45,7 +45,7 @@ protected function buildWhereClause() { // Legacy support for old filter syntax per the test contract. // (Convert the style to the later one & then deal with them). $filterArray = explode('.', $key); - $value = array($filterArray[1] => $value); + $value = [$filterArray[1] => $value]; $key = 'filters'; } @@ -54,12 +54,12 @@ protected function buildWhereClause() { foreach ($value as $filterKey => $filterValue) { if (substr($filterKey, -4, 4) == 'high') { $key = substr($filterKey, 0, -5); - $value = array('<=' => $filterValue); + $value = ['<=' => $filterValue]; } if (substr($filterKey, -3, 3) == 'low') { $key = substr($filterKey, 0, -4); - $value = array('>=' => $filterValue); + $value = ['>=' => $filterValue]; } if ($filterKey == 'is_current' || $filterKey == 'isCurrent') { @@ -89,7 +89,10 @@ protected function buildWhereClause() { $column_name = $key; } elseif (($cf_id = \CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) { - list($table_name, $column_name) = $this->addCustomField($this->apiFieldSpec['custom_' . $cf_id], 'INNER'); + // If we check a custom field on 'IS NULL', it should also work when there is no + // record in the custom value table, see CRM-20740. + $side = empty($value['IS NULL']) ? 'INNER' : 'LEFT OUTER'; + list($table_name, $column_name) = $this->addCustomField($this->apiFieldSpec['custom_' . $cf_id], $side); } elseif (strpos($key, '.')) { $fkInfo = $this->addFkField($key, 'INNER'); @@ -107,7 +110,7 @@ protected function buildWhereClause() { } $operator = is_array($value) ? \CRM_Utils_Array::first(array_keys($value)) : NULL; if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) { - $value = array('=' => $value); + $value = ['=' => $value]; } $filters[$key] = \CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value); } @@ -118,10 +121,10 @@ protected function buildWhereClause() { $orGroups = array_map('trim', explode(',', $orGroups)); } if (!is_array(\CRM_Utils_Array::first($orGroups))) { - $orGroups = array($orGroups); + $orGroups = [$orGroups]; } foreach ($orGroups as $orGroup) { - $orClause = array(); + $orClause = []; foreach ($orGroup as $key) { if (!isset($filters[$key])) { throw new \CiviCRM_API3_Exception("'$key' specified in OR group but not added to params"); @@ -144,11 +147,11 @@ protected function buildWhereClause() { protected function getFields() { require_once 'api/v3/Generic.php'; // Call this function directly instead of using the api wrapper to force unique field names off - $apiSpec = \civicrm_api3_generic_getfields(array( + $apiSpec = \civicrm_api3_generic_getfields([ 'entity' => $this->entity, 'version' => 3, - 'params' => array('action' => 'get'), - ), FALSE); + 'params' => ['action' => 'get'], + ], FALSE); return $apiSpec['values']; } @@ -171,7 +174,7 @@ protected function getField($fieldName) { foreach ($this->apiFieldSpec as $field) { if ( $fieldName == \CRM_Utils_Array::value('uniqueName', $field) || - array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, array())) !== FALSE + array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, [])) !== FALSE ) { return $field; } diff --git a/Civi/API/Event/AuthorizeEvent.php b/Civi/API/Event/AuthorizeEvent.php index 418642cb65d8..4bc99b730493 100644 --- a/Civi/API/Event/AuthorizeEvent.php +++ b/Civi/API/Event/AuthorizeEvent.php @@ -1,9 +1,9 @@ apiRequest; } + /** + * Create a brief string identifying the entity/action. Useful for + * pithy matching/switching. + * + * Ex: if ($e->getApiRequestSig() === '3.contact.get') { ... } + * + * @return string + * Ex: '3.contact.get' + */ + public function getApiRequestSig() { + return mb_strtolower($this->apiRequest['version'] . '.' . $this->apiRequest['entity'] . '.' . $this->apiRequest['action']); + } + } diff --git a/Civi/API/Event/ExceptionEvent.php b/Civi/API/Event/ExceptionEvent.php index 2c04398864db..e608ebc2bae8 100644 --- a/Civi/API/Event/ExceptionEvent.php +++ b/Civi/API/Event/ExceptionEvent.php @@ -1,9 +1,9 @@ wrapApi(function($apiRequest, $continue){ + * echo "Hello\n"; + * $continue($apiRequest); + * echo "Goodbye\n"; + * }); + * + * @param callable $callback + * The custom API implementation. + * Function(array $apiRequest, callable $continue). + * @return PrepareEvent + */ + public function wrapApi($callback) { + $this->apiProvider = new WrappingProvider($callback, $this->apiProvider); + return $this; + } + } diff --git a/Civi/API/Event/ResolveEvent.php b/Civi/API/Event/ResolveEvent.php index 75d8192d248b..396418649173 100644 --- a/Civi/API/Event/ResolveEvent.php +++ b/Civi/API/Event/ResolveEvent.php @@ -1,9 +1,9 @@ */ public static function allEvents() { - return array( + return [ self::AUTHORIZE, self::EXCEPTION, self::PREPARE, self::RESOLVE, self::RESPOND, - ); + ]; } /** diff --git a/Civi/API/Exception/NotImplementedException.php b/Civi/API/Exception/NotImplementedException.php index f2df795ca32a..b8f253f13ebd 100644 --- a/Civi/API/Exception/NotImplementedException.php +++ b/Civi/API/Exception/NotImplementedException.php @@ -8,6 +8,7 @@ * @package Civi\API\Exception */ class NotImplementedException extends \API_Exception { + /** * @param string $message * The human friendly error message. @@ -18,7 +19,7 @@ class NotImplementedException extends \API_Exception { * @param \Exception|NULL $previous * A previous exception which caused this new exception. */ - public function __construct($message, $extraParams = array(), \Exception $previous = NULL) { + public function __construct($message, $extraParams = [], \Exception $previous = NULL) { parent::__construct($message, \API_Exception::NOT_IMPLEMENTED, $extraParams, $previous); } diff --git a/Civi/API/Exception/UnauthorizedException.php b/Civi/API/Exception/UnauthorizedException.php index 0235f1f4408e..4e3bdbf7379e 100644 --- a/Civi/API/Exception/UnauthorizedException.php +++ b/Civi/API/Exception/UnauthorizedException.php @@ -8,6 +8,7 @@ * @package Civi\API\Exception */ class UnauthorizedException extends \API_Exception { + /** * @param string $message * The human friendly error message. @@ -18,7 +19,7 @@ class UnauthorizedException extends \API_Exception { * @param \Exception|NULL $previous * A previous exception which caused this new exception. */ - public function __construct($message, $extraParams = array(), \Exception $previous = NULL) { + public function __construct($message, $extraParams = [], \Exception $previous = NULL) { parent::__construct($message, \API_Exception::UNAUTHORIZED, $extraParams, $previous); } diff --git a/Civi/API/ExternalBatch.php b/Civi/API/ExternalBatch.php index 6a8d463e14a1..f9ea3ae69cf2 100644 --- a/Civi/API/ExternalBatch.php +++ b/Civi/API/ExternalBatch.php @@ -46,7 +46,7 @@ class ExternalBatch { * @param array $defaultParams * Default values to merge into any API calls. */ - public function __construct($defaultParams = array()) { + public function __construct($defaultParams = []) { global $civicrm_root; $this->root = $civicrm_root; $this->settingsPath = defined('CIVICRM_SETTINGS_PATH') ? CIVICRM_SETTINGS_PATH : NULL; @@ -65,14 +65,14 @@ public function __construct($defaultParams = array()) { * @param array $params * @return ExternalBatch */ - public function addCall($entity, $action, $params = array()) { + public function addCall($entity, $action, $params = []) { $params = array_merge($this->defaultParams, $params); - $this->apiCalls[] = array( + $this->apiCalls[] = [ 'entity' => $entity, 'action' => $action, 'params' => $params, - ); + ]; return $this; } @@ -119,20 +119,20 @@ public function wait() { while (!empty($this->processes)) { usleep(self::POLL_INTERVAL); foreach (array_keys($this->processes) as $idx) { - /** @var Process $process */ + /** @var \Symfony\Component\Process\Process $process */ $process = $this->processes[$idx]; if (!$process->isRunning()) { $parsed = json_decode($process->getOutput(), TRUE); if ($process->getExitCode() || $parsed === NULL) { - $this->apiResults[] = array( + $this->apiResults[] = [ 'is_error' => 1, 'error_message' => 'External API returned malformed response.', - 'trace' => array( + 'trace' => [ 'code' => $process->getExitCode(), 'stdout' => $process->getOutput(), 'stderr' => $process->getErrorOutput(), - ), - ); + ], + ]; } else { $this->apiResults[] = $parsed; @@ -179,11 +179,11 @@ public function isSupported() { /** * @param array $apiCall * Array with keys: entity, action, params. - * @return Process + * @return \Symfony\Component\Process\Process * @throws \CRM_Core_Exception */ public function createProcess($apiCall) { - $parts = array(); + $parts = []; if (defined('CIVICRM_TEST') && CIVICRM_TEST) { // When testing, civicrm.settings.php may rely on $_CV, which is only @@ -214,9 +214,9 @@ public function createProcess($apiCall) { } $command = implode(" ", $parts); - $env = array_merge($this->env, array( + $env = array_merge($this->env, [ 'CIVICRM_SETTINGS' => $this->settingsPath, - )); + ]); return new Process($command, $this->root, $env); } diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php index 3571201d80ce..e5d399021745 100644 --- a/Civi/API/Kernel.php +++ b/Civi/API/Kernel.php @@ -1,9 +1,9 @@ apiProviders = $apiProviders; $this->dispatcher = $dispatcher; } @@ -153,7 +152,7 @@ public function runAuthorize($entity, $action, $params, $extra = NULL) { * * The request must be in canonical format. Exceptions will be propagated out. * - * @param $apiRequest + * @param array $apiRequest * @return array * @throws \API_Exception * @throws \Civi\API\Exception\NotImplementedException @@ -165,7 +164,7 @@ public function runRequest($apiRequest) { list($apiProvider, $apiRequest) = $this->resolve($apiRequest); $this->authorize($apiProvider, $apiRequest); - $apiRequest = $this->prepare($apiProvider, $apiRequest); + list ($apiProvider, $apiRequest) = $this->prepare($apiProvider, $apiRequest); $result = $apiProvider->invoke($apiRequest); return $this->respond($apiProvider, $apiRequest, $result); @@ -200,7 +199,7 @@ public function boot($apiRequest) { } /** - * @param $apiRequest + * @param array $apiRequest * @throws \API_Exception */ protected function validate($apiRequest) { @@ -213,29 +212,30 @@ protected function validate($apiRequest) { * The full description of the API request. * @throws Exception\NotImplementedException * @return array - * Array(0 => ProviderInterface, 1 => array). + * A tuple with the provider-object and a revised apiRequest. + * Array(0 => ProviderInterface, 1 => array $apiRequest). */ public function resolve($apiRequest) { - /** @var ResolveEvent $resolveEvent */ + /** @var \Civi\API\Event\ResolveEvent $resolveEvent */ $resolveEvent = $this->dispatcher->dispatch(Events::RESOLVE, new ResolveEvent($apiRequest, $this)); $apiRequest = $resolveEvent->getApiRequest(); if (!$resolveEvent->getApiProvider()) { throw new \Civi\API\Exception\NotImplementedException("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); } - return array($resolveEvent->getApiProvider(), $apiRequest); + return [$resolveEvent->getApiProvider(), $apiRequest]; } /** * Determine if the API request is allowed (under current policy) * - * @param ProviderInterface $apiProvider + * @param \Civi\API\Provider\ProviderInterface $apiProvider * The API provider responsible for executing the request. * @param array $apiRequest * The full description of the API request. * @throws Exception\UnauthorizedException */ public function authorize($apiProvider, $apiRequest) { - /** @var AuthorizeEvent $event */ + /** @var \Civi\API\Event\AuthorizeEvent $event */ $event = $this->dispatcher->dispatch(Events::AUTHORIZE, new AuthorizeEvent($apiProvider, $apiRequest, $this)); if (!$event->isAuthorized()) { throw new \Civi\API\Exception\UnauthorizedException("Authorization failed"); @@ -245,31 +245,34 @@ public function authorize($apiProvider, $apiRequest) { /** * Allow third-party code to manipulate the API request before execution. * - * @param ProviderInterface $apiProvider + * @param \Civi\API\Provider\ProviderInterface $apiProvider * The API provider responsible for executing the request. * @param array $apiRequest * The full description of the API request. - * @return mixed + * @return array + * [0 => ProviderInterface $provider, 1 => array $apiRequest] + * The revised API request. */ public function prepare($apiProvider, $apiRequest) { - /** @var PrepareEvent $event */ + /** @var \Civi\API\Event\PrepareEvent $event */ $event = $this->dispatcher->dispatch(Events::PREPARE, new PrepareEvent($apiProvider, $apiRequest, $this)); - return $event->getApiRequest(); + return [$event->getApiProvider(), $event->getApiRequest()]; } /** * Allow third-party code to manipulate the API response after execution. * - * @param ProviderInterface $apiProvider + * @param \Civi\API\Provider\ProviderInterface $apiProvider * The API provider responsible for executing the request. * @param array $apiRequest * The full description of the API request. * @param array $result * The response to return to the client. * @return mixed + * The revised $result. */ public function respond($apiProvider, $apiRequest, $result) { - /** @var RespondEvent $event */ + /** @var \Civi\API\Event\RespondEvent $event */ $event = $this->dispatcher->dispatch(Events::RESPOND, new RespondEvent($apiProvider, $apiRequest, $result, $this)); return $event->getResponse(); } @@ -282,9 +285,9 @@ public function respond($apiProvider, $apiRequest, $result) { */ public function getEntityNames($version) { // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher? - $entityNames = array(); + $entityNames = []; foreach ($this->getApiProviders() as $provider) { - /** @var ProviderInterface $provider */ + /** @var \Civi\API\Provider\ProviderInterface $provider */ $entityNames = array_merge($entityNames, $provider->getEntityNames($version)); } $entityNames = array_unique($entityNames); @@ -302,9 +305,9 @@ public function getEntityNames($version) { */ public function getActionNames($version, $entity) { // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher? - $actionNames = array(); + $actionNames = []; foreach ($this->getApiProviders() as $provider) { - /** @var ProviderInterface $provider */ + /** @var \Civi\API\Provider\ProviderInterface $provider */ $actionNames = array_merge($actionNames, $provider->getActionNames($version, $entity)); } $actionNames = array_unique($actionNames); @@ -321,7 +324,7 @@ public function getActionNames($version, $entity) { * API response. */ public function formatException($e, $apiRequest) { - $data = array(); + $data = []; if (!empty($apiRequest['params']['debug'])) { $data['trace'] = $e->getTraceAsString(); } @@ -342,7 +345,8 @@ public function formatApiException($e, $apiRequest) { $data['action'] = \CRM_Utils_Array::value('action', $apiRequest); if (\CRM_Utils_Array::value('debug', \CRM_Utils_Array::value('params', $apiRequest)) - && empty($data['trace']) // prevent recursion + // prevent recursion + && empty($data['trace']) ) { $data['trace'] = $e->getTraceAsString(); } @@ -359,7 +363,7 @@ public function formatApiException($e, $apiRequest) { * API response. */ public function formatPearException($e, $apiRequest) { - $data = array(); + $data = []; $error = $e->getCause(); if ($error instanceof \DB_Error) { $data["error_code"] = \DB::errorMessage($error->getCode()); @@ -407,7 +411,7 @@ public function createError($msg, $data, $apiRequest, $code = NULL) { } } - $data = civicrm_api3_create_error($msg, $data); + $data = \civicrm_api3_create_error($msg, $data); if (isset($apiRequest['params']) && is_array($apiRequest['params']) && !empty($apiRequest['params']['api.has_parent'])) { $errorCode = empty($data['error_code']) ? 'chained_api_failed' : $data['error_code']; @@ -456,7 +460,7 @@ public function setApiProviders($apiProviders) { } /** - * @param ProviderInterface $apiProvider + * @param \Civi\API\Provider\ProviderInterface $apiProvider * The API provider responsible for executing the request. * @return Kernel */ diff --git a/Civi/API/Provider/AdhocProvider.php b/Civi/API/Provider/AdhocProvider.php index 55c4df3b2b3b..4a03ed04864c 100644 --- a/Civi/API/Provider/AdhocProvider.php +++ b/Civi/API/Provider/AdhocProvider.php @@ -1,9 +1,9 @@ array( - array('onApiResolve', Events::W_EARLY), - ), - Events::AUTHORIZE => array( - array('onApiAuthorize', Events::W_EARLY), - ), - ); + return [ + Events::RESOLVE => [ + ['onApiResolve', Events::W_EARLY], + ], + Events::AUTHORIZE => [ + ['onApiAuthorize', Events::W_EARLY], + ], + ]; } /** * @var array (string $name => array('perm' => string, 'callback' => callable)) */ - protected $actions = array(); + protected $actions = []; /** * @var string @@ -90,10 +90,10 @@ public function __construct($version, $entity) { * @return AdhocProvider */ public function addAction($name, $perm, $callback) { - $this->actions[strtolower($name)] = array( + $this->actions[strtolower($name)] = [ 'perm' => $perm, 'callback' => $callback, - ); + ]; return $this; } @@ -137,7 +137,7 @@ public function invoke($apiRequest) { * @return array */ public function getEntityNames($version) { - return array($this->entity); + return [$this->entity]; } /** @@ -151,7 +151,7 @@ public function getActionNames($version, $entity) { return array_keys($this->actions); } else { - return array(); + return []; } } diff --git a/Civi/API/Provider/MagicFunctionProvider.php b/Civi/API/Provider/MagicFunctionProvider.php index 404c2de2bc79..346b47035987 100644 --- a/Civi/API/Provider/MagicFunctionProvider.php +++ b/Civi/API/Provider/MagicFunctionProvider.php @@ -1,9 +1,9 @@ array( - array('onApiResolve', Events::W_MIDDLE), - ), - ); + return [ + Events::RESOLVE => [ + ['onApiResolve', Events::W_MIDDLE], + ], + ]; } /** @@ -54,7 +55,7 @@ public static function getSubscribedEvents() { /** */ public function __construct() { - $this->cache = array(); + $this->cache = []; } /** @@ -83,7 +84,18 @@ public function invoke($apiRequest) { // Unlike normal API implementations, generic implementations require explicit // knowledge of the entity and action (as well as $params). Bundle up these bits // into a convenient data structure. + if ($apiRequest['action'] === 'getsingle') { + // strip any api nested parts here as otherwise chaining may happen twice + // see https://lab.civicrm.org/dev/core/issues/643 + // testCreateBAODefaults fails without this. + foreach ($apiRequest['params'] as $key => $param) { + if ($key !== 'api.has_parent' && substr($key, 0, 4) === 'api.' || substr($key, 0, 4) === 'api_') { + unset($apiRequest['params'][$key]); + } + } + } $result = $function($apiRequest); + } elseif ($apiRequest['function'] && !$apiRequest['is_generic']) { $result = $function($apiRequest['params']); @@ -97,12 +109,12 @@ public function invoke($apiRequest) { * @return array */ public function getEntityNames($version) { - $entities = array(); + $entities = []; $include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path())); #$include_dirs = array(dirname(__FILE__). '/../../'); foreach ($include_dirs as $include_dir) { $api_dir = implode(DIRECTORY_SEPARATOR, - array($include_dir, 'api', 'v' . $version)); + [$include_dir, 'api', 'v' . $version]); if (!is_dir($api_dir)) { continue; } @@ -126,7 +138,7 @@ public function getEntityNames($version) { } } } - $entities = array_diff($entities, array('Generic')); + $entities = array_diff($entities, ['Generic']); $entities = array_unique($entities); sort($entities); @@ -143,12 +155,12 @@ public function getActionNames($version, $entity) { $entity = _civicrm_api_get_camel_name($entity); $entities = $this->getEntityNames($version); if (!in_array($entity, $entities)) { - return array(); + return []; } $this->loadEntity($entity, $version); $functions = get_defined_functions(); - $actions = array(); + $actions = []; $prefix = 'civicrm_api' . $version . '_' . _civicrm_api_get_entity_name_from_camel($entity) . '_'; $prefixGeneric = 'civicrm_api' . $version . '_generic_'; foreach ($functions['user'] as $fct) { @@ -192,21 +204,21 @@ protected function resolve($apiRequest) { // someone already loaded the appropriate file // FIXME: This has the affect of masking bugs in load order; this is // included to provide bug-compatibility. - $this->cache[$cachekey] = array('function' => $stdFunction, 'is_generic' => FALSE); + $this->cache[$cachekey] = ['function' => $stdFunction, 'is_generic' => FALSE]; return $this->cache[$cachekey]; } - $stdFiles = array( + $stdFiles = [ // By convention, the $camelName.php is more likely to contain the // function, so test it first 'api/v' . $apiRequest['version'] . '/' . $camelName . '.php', 'api/v' . $apiRequest['version'] . '/' . $camelName . '/' . $actionCamelName . '.php', - ); + ]; foreach ($stdFiles as $stdFile) { if (\CRM_Utils_File::isIncludable($stdFile)) { require_once $stdFile; if (function_exists($stdFunction)) { - $this->cache[$cachekey] = array('function' => $stdFunction, 'is_generic' => FALSE); + $this->cache[$cachekey] = ['function' => $stdFunction, 'is_generic' => FALSE]; return $this->cache[$cachekey]; } } @@ -216,23 +228,23 @@ protected function resolve($apiRequest) { require_once 'api/v3/Generic.php'; # $genericFunction = 'civicrm_api3_generic_' . $apiRequest['action']; $genericFunction = $this->getFunctionName('generic', $apiRequest['action'], $apiRequest['version']); - $genericFiles = array( + $genericFiles = [ // By convention, the Generic.php is more likely to contain the // function, so test it first 'api/v' . $apiRequest['version'] . '/Generic.php', 'api/v' . $apiRequest['version'] . '/Generic/' . $actionCamelName . '.php', - ); + ]; foreach ($genericFiles as $genericFile) { if (\CRM_Utils_File::isIncludable($genericFile)) { require_once $genericFile; if (function_exists($genericFunction)) { - $this->cache[$cachekey] = array('function' => $genericFunction, 'is_generic' => TRUE); + $this->cache[$cachekey] = ['function' => $genericFunction, 'is_generic' => TRUE]; return $this->cache[$cachekey]; } } } - $this->cache[$cachekey] = array('function' => FALSE, 'is_generic' => FALSE); + $this->cache[$cachekey] = ['function' => FALSE, 'is_generic' => FALSE]; return $this->cache[$cachekey]; } @@ -274,12 +286,13 @@ protected function loadEntity($entity, $version) { } // Check for standalone action files; to match _civicrm_api_resolve(), only load the first one - $loaded_files = array(); // array($relativeFilePath => TRUE) + // array($relativeFilePath => TRUE) + $loaded_files = []; $include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path())); foreach ($include_dirs as $include_dir) { - foreach (array($camelName, 'Generic') as $name) { + foreach ([$camelName, 'Generic'] as $name) { $action_dir = implode(DIRECTORY_SEPARATOR, - array($include_dir, 'api', "v${version}", $name)); + [$include_dir, 'api', "v${version}", $name]); if (!is_dir($action_dir)) { continue; } @@ -288,7 +301,8 @@ protected function loadEntity($entity, $version) { foreach ($iterator as $fileinfo) { $file = $fileinfo->getFilename(); if (array_key_exists($file, $loaded_files)) { - continue; // action provided by an earlier item on include_path + // action provided by an earlier item on include_path + continue; } $parts = explode(".", $file); diff --git a/Civi/API/Provider/ProviderInterface.php b/Civi/API/Provider/ProviderInterface.php index 71669fd41e4f..4fbc8b7e316a 100644 --- a/Civi/API/Provider/ProviderInterface.php +++ b/Civi/API/Provider/ProviderInterface.php @@ -1,9 +1,9 @@ array( + return [ + Events::RESOLVE => [ // TODO decide if we really want to override others - array('onApiResolve', Events::W_EARLY), - ), - Events::AUTHORIZE => array( + ['onApiResolve', Events::W_EARLY], + ], + Events::AUTHORIZE => [ // TODO decide if we really want to override others - array('onApiAuthorize', Events::W_EARLY), - ), - ); + ['onApiAuthorize', Events::W_EARLY], + ], + ]; } /** @@ -66,10 +67,11 @@ public static function getSubscribedEvents() { */ public function __construct($apiKernel) { $this->apiKernel = $apiKernel; - $this->actions = array( - 'Entity' => array('get', 'getactions'), - '*' => array('getactions'), // 'getfields' - ); + $this->actions = [ + 'Entity' => ['get', 'getactions'], + // 'getfields' + '*' => ['getactions'], + ]; } /** @@ -133,7 +135,7 @@ public function invoke($apiRequest) { * @return array */ public function getEntityNames($version) { - return array('Entity'); + return ['Entity']; } /** diff --git a/Civi/API/Provider/StaticProvider.php b/Civi/API/Provider/StaticProvider.php index dc94db7090d4..0a10aee85a95 100644 --- a/Civi/API/Provider/StaticProvider.php +++ b/Civi/API/Provider/StaticProvider.php @@ -1,9 +1,9 @@ array( - array('onApiResolve', Events::W_MIDDLE), - ), - Events::AUTHORIZE => array( - array('onApiAuthorize', Events::W_MIDDLE), - ), - ); + return [ + Events::RESOLVE => [ + ['onApiResolve', Events::W_MIDDLE], + ], + Events::AUTHORIZE => [ + ['onApiAuthorize', Events::W_MIDDLE], + ], + ]; } /** @@ -66,21 +66,21 @@ public static function getSubscribedEvents() { * @param array $records * List of mock records to be read/updated by API calls. */ - public function __construct($version, $entity, $fields, $perms = array(), $records = array()) { + public function __construct($version, $entity, $fields, $perms = [], $records = []) { parent::__construct($version, $entity); - $perms = array_merge(array( + $perms = array_merge([ 'create' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, 'get' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, 'delete' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, - ), $perms); + ], $perms); - $this->records = \CRM_Utils_Array::index(array('id'), $records); + $this->records = \CRM_Utils_Array::index(['id'], $records); $this->fields = $fields; - $this->addAction('create', $perms['create'], array($this, 'doCreate')); - $this->addAction('get', $perms['get'], array($this, 'doGet')); - $this->addAction('delete', $perms['delete'], array($this, 'doDelete')); + $this->addAction('create', $perms['create'], [$this, 'doCreate']); + $this->addAction('get', $perms['get'], [$this, 'doGet']); + $this->addAction('delete', $perms['delete'], [$this, 'doDelete']); } /** @@ -102,7 +102,7 @@ public function setRecords($records) { * @param array $apiRequest * The full description of the API request. * @return array - * Formatted API result + * Formatted API result * @throws \API_Exception */ public function doCreate($apiRequest) { @@ -111,7 +111,7 @@ public function doCreate($apiRequest) { } else { $id = max(array_keys($this->records)) + 1; - $this->records[$id] = array(); + $this->records[$id] = []; } if (!isset($this->records[$id])) { @@ -131,7 +131,7 @@ public function doCreate($apiRequest) { * @param array $apiRequest * The full description of the API request. * @return array - * Formatted API result + * Formatted API result * @throws \API_Exception */ public function doGet($apiRequest) { @@ -142,7 +142,7 @@ public function doGet($apiRequest) { * @param array $apiRequest * The full description of the API request. * @return array - * Formatted API result + * Formatted API result * @throws \API_Exception */ public function doDelete($apiRequest) { @@ -150,7 +150,7 @@ public function doDelete($apiRequest) { if ($id && isset($this->records[$id])) { unset($this->records[$id]); } - return civicrm_api3_create_success(array()); + return civicrm_api3_create_success([]); } } diff --git a/Civi/API/Provider/WrappingProvider.php b/Civi/API/Provider/WrappingProvider.php new file mode 100644 index 000000000000..d2c08c5dd1e5 --- /dev/null +++ b/Civi/API/Provider/WrappingProvider.php @@ -0,0 +1,80 @@ +callback = $callback; + $this->original = $original; + } + + public function invoke($apiRequest) { + // $continue = function($a) { return $this->original->invoke($a); }; + $continue = [$this->original, 'invoke']; + return call_user_func($this->callback, $apiRequest, $continue); + } + + public function getEntityNames($version) { + // return $version == $this->version ? [$this->entity] : []; + throw new \API_Exception("Not support: WrappingProvider::getEntityNames()"); + } + + public function getActionNames($version, $entity) { + // return $version == $this->version && $this->entity == $entity ? [$this->action] : []; + throw new \API_Exception("Not support: WrappingProvider::getActionNames()"); + } + +} diff --git a/Civi/API/Request.php b/Civi/API/Request.php index d44f769a6b80..9a101002a50f 100644 --- a/Civi/API/Request.php +++ b/Civi/API/Request.php @@ -1,9 +1,9 @@ apiFieldSpec = $this->getFields(); $this->query = \CRM_Utils_SQL_Select::from($bao->tableName() . ' ' . self::MAIN_TABLE_ALIAS); - $bao->free(); // Add ACLs first to avoid redundant subclauses $this->checkPermissions = $checkPermissions; @@ -139,15 +139,14 @@ public function run() { $this->query->limit($this->limit, $this->offset); } - $result_entities = array(); + $result_entities = []; $result_dao = \CRM_Core_DAO::executeQuery($this->query->toSQL()); while ($result_dao->fetch()) { if (in_array('count_rows', $this->select)) { - $result_dao->free(); return (int) $result_dao->c; } - $result_entities[$result_dao->id] = array(); + $result_entities[$result_dao->id] = []; foreach ($this->selectFields as $column => $alias) { $returnName = $alias; $alias = str_replace('.', '_', $alias); @@ -165,7 +164,6 @@ public function run() { } }; } - $result_dao->free(); return $result_entities; } @@ -240,7 +238,7 @@ protected function addFkField($fkFieldName, $side) { // Add acl condition $joinCondition = array_merge( - array("$prev.$fk = $tableAlias.$keyColumn"), + ["$prev.$fk = $tableAlias.$keyColumn"], $this->getAclClause($tableAlias, \_civicrm_api3_get_BAO($fkField['FKApiName']), $subStack) ); @@ -259,7 +257,7 @@ protected function addFkField($fkFieldName, $side) { $fkField = &$fkField['FKApiSpec'][$fieldName]; $prev = $tableAlias; } - return array($tableAlias, $fieldName); + return [$tableAlias, $fieldName]; } /** @@ -300,8 +298,8 @@ protected function addCustomField($customField, $side, $baseTable = self::MAIN_T $tableName = $customField["table_name"]; $columnName = $customField["column_name"]; $tableAlias = "{$baseTable}_to_$tableName"; - $this->join($side, $tableName, $tableAlias, array("`$tableAlias`.entity_id = `$baseTable`.id")); - return array($tableAlias, $columnName); + $this->join($side, $tableName, $tableAlias, ["`$tableAlias`.entity_id = `$baseTable`.id"]); + return [$tableAlias, $columnName]; } /** @@ -330,7 +328,7 @@ protected function validateNestedInput($fieldName, &$value) { $entity = $spec[$name]['FKApiName']; $spec = $spec[$name]['FKApiSpec']; } - $params = array($fieldName => $value); + $params = [$fieldName => $value]; \_civicrm_api3_validate_fields($entity, 'get', $params, $spec); $value = $params[$fieldName]; } @@ -348,11 +346,11 @@ protected function checkPermissionToJoin($entity, $fieldStack) { return TRUE; } // Build an array of params that relate to the joined entity - $params = array( + $params = [ 'version' => 3, - 'return' => array(), + 'return' => [], 'check_permissions' => $this->checkPermissions, - ); + ]; $prefix = implode('.', $fieldStack) . '.'; $len = strlen($prefix); foreach ($this->select as $key => $ret) { @@ -377,15 +375,15 @@ protected function checkPermissionToJoin($entity, $fieldStack) { * @param array $stack * @return array */ - protected function getAclClause($tableAlias, $baoName, $stack = array()) { + protected function getAclClause($tableAlias, $baoName, $stack = []) { if (!$this->checkPermissions) { - return array(); + return []; } // Prevent (most) redundant acl sub clauses if they have already been applied to the main entity. // FIXME: Currently this only works 1 level deep, but tracking through multiple joins would increase complexity // and just doing it for the first join takes care of most acl clause deduping. if (count($stack) === 1 && in_array($stack[0], $this->aclFields)) { - return array(); + return []; } $clauses = $baoName::getSelectWhereClause($tableAlias); if (!$stack) { @@ -467,8 +465,10 @@ protected function buildSelectFields() { } } - // Always select the ID. - $this->selectFields[self::MAIN_TABLE_ALIAS . ".id"] = "id"; + // Always select the ID if the table has one. + if (array_key_exists('id', $this->apiFieldSpec)) { + $this->selectFields[self::MAIN_TABLE_ALIAS . ".id"] = "id"; + } // core return fields foreach ($return as $fieldName) { diff --git a/Civi/API/Subscriber/APIv3SchemaAdapter.php b/Civi/API/Subscriber/APIv3SchemaAdapter.php index d170827c93df..3831cdf4d8b9 100644 --- a/Civi/API/Subscriber/APIv3SchemaAdapter.php +++ b/Civi/API/Subscriber/APIv3SchemaAdapter.php @@ -1,9 +1,9 @@ array( - array('onApiPrepare', Events::W_MIDDLE), - array('onApiPrepare_validate', Events::W_LATE), - ), - ); + return [ + Events::PREPARE => [ + ['onApiPrepare', Events::W_MIDDLE], + ['onApiPrepare_validate', Events::W_LATE], + ], + ]; } /** @@ -81,6 +82,9 @@ public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) { */ public function onApiPrepare_validate(\Civi\API\Event\Event $event) { $apiRequest = $event->getApiRequest(); + if ($apiRequest['version'] > 3) { + return; + } // Not sure why this is omitted for generic actions. It would make sense // to omit 'getfields', but that's only one generic action. @@ -96,7 +100,7 @@ public function onApiPrepare_validate(\Civi\API\Event\Event $event) { * @return array */ public function getDefaults($fields) { - $defaults = array(); + $defaults = []; foreach ($fields as $field => $values) { if (isset($values['api.default'])) { @@ -112,7 +116,7 @@ public function getDefaults($fields) { * @return array */ public function getRequired($fields) { - $required = array('version'); + $required = ['version']; foreach ($fields as $field => $values) { if (!empty($values['api.required'])) { diff --git a/Civi/API/Subscriber/ChainSubscriber.php b/Civi/API/Subscriber/ChainSubscriber.php index 663eee14f3b4..84b55753a0e9 100644 --- a/Civi/API/Subscriber/ChainSubscriber.php +++ b/Civi/API/Subscriber/ChainSubscriber.php @@ -1,9 +1,9 @@ array('onApiRespond', Events::W_EARLY), - ); + return [ + Events::RESPOND => ['onApiRespond', Events::W_EARLY], + ]; } /** @@ -93,7 +94,7 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit // We don't need to worry about nested api in the getfields/getoptions // actions, so just return immediately. - if (in_array($action, array('getfields', 'getfield', 'getoptions'))) { + if (in_array($action, ['getfields', 'getfield', 'getoptions'])) { return; } @@ -102,7 +103,7 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit // $result to be a recursive array // $result['values'][0] = $result; $oldResult = $result; - $result = array('values' => array(0 => $oldResult)); + $result = ['values' => [0 => $oldResult]]; } foreach ($params as $field => $newparams) { if ((is_array($newparams) || $newparams === 1) && $field <> 'api.has_parent' && substr($field, 0, 3) == 'api') { @@ -110,7 +111,7 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit // 'api.participant.delete' => 1 is a valid options - handle 1 // instead of an array if ($newparams === 1) { - $newparams = array('version' => $version); + $newparams = ['version' => $version]; } // can be api_ or api. $separator = $field[3]; @@ -120,18 +121,18 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit $subAPI = explode($separator, $field); $subaction = empty($subAPI[2]) ? $action : $subAPI[2]; - $subParams = array( + $subParams = [ 'debug' => \CRM_Utils_Array::value('debug', $params), - ); + ]; $subEntity = _civicrm_api_get_entity_name_from_camel($subAPI[1]); // Hard coded list of entitys that have fields starting api_ and shouldn't be automatically // deemed to be chained API calls - $skipList = array( - 'SmsProvider' => array('type', 'url', 'params'), - 'Job' => array('prefix', 'entity', 'action'), - 'Contact' => array('key'), - ); + $skipList = [ + 'SmsProvider' => ['type', 'url', 'params'], + 'Job' => ['prefix', 'entity', 'action'], + 'Contact' => ['key'], + ]; if (isset($skipList[$entity]) && in_array($subEntity, $skipList[$entity])) { continue; } @@ -150,7 +151,7 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit $subParams['entity_table'] = 'civicrm_' . $lowercase_entity; } - $crm16084 = FALSE; + $addEntityId = TRUE; if ($subEntity == 'relationship' && $lowercase_entity == 'contact') { // if a relationship call is chained to a contact call, we need // to check whether contact_id_a or contact_id_b for the @@ -159,12 +160,12 @@ protected function callNestedApi($apiKernel, &$params, &$result, $action, $entit // See CRM-16084. foreach (array_keys($newparams) as $key) { if (substr($key, 0, 11) == 'contact_id_') { - $crm16084 = TRUE; + $addEntityId = FALSE; break; } } } - if (!$crm16084) { + if ($addEntityId) { $subParams[$lowercase_entity . "_id"] = $parentAPIValues['id']; } } diff --git a/Civi/API/Subscriber/DynamicFKAuthorization.php b/Civi/API/Subscriber/DynamicFKAuthorization.php index ec6c8c01f5bb..94398ac0caa9 100644 --- a/Civi/API/Subscriber/DynamicFKAuthorization.php +++ b/Civi/API/Subscriber/DynamicFKAuthorization.php @@ -1,9 +1,9 @@ array( - array('onApiAuthorize', Events::W_EARLY), - ), - ); + return [ + Events::AUTHORIZE => [ + ['onApiAuthorize', Events::W_EARLY], + ], + ]; } /** @@ -146,7 +146,7 @@ public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { $apiRequest = $event->getApiRequest(); if ($apiRequest['version'] == 3 && \CRM_Utils_String::convertStringToCamel($apiRequest['entity']) == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) { if (isset($apiRequest['params']['field_name'])) { - $fldIdx = \CRM_Utils_Array::index(array('field_name'), $this->getCustomFields()); + $fldIdx = \CRM_Utils_Array::index(['field_name'], $this->getCustomFields()); if (empty($fldIdx[$apiRequest['params']['field_name']])) { throw new \Exception("Failed to map custom field to entity table"); } @@ -179,6 +179,9 @@ public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { } if (isset($apiRequest['params']['entity_table'])) { + if (!\CRM_Core_DAO_AllCoreTables::isCoreTable($apiRequest['params']['entity_table'])) { + throw new \API_Exception("Unrecognized target entity table {$apiRequest['params']['entity_table']}"); + } $this->authorizeDelegate( $apiRequest['action'], $apiRequest['params']['entity_table'], @@ -197,7 +200,7 @@ public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { * The API action (e.g. "create"). * @param string $entityTable * The target entity table (e.g. "civicrm_mailing"). - * @param int|NULL $entityId + * @param int|null $entityId * The target entity ID. * @param array $apiRequest * The full API request. @@ -206,6 +209,10 @@ public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { * @throws \Civi\API\Exception\UnauthorizedException */ public function authorizeDelegate($action, $entityTable, $entityId, $apiRequest) { + if ($this->isTrusted($apiRequest)) { + return; + } + $entity = $this->getDelegatedEntityName($entityTable); if (!$entity) { throw new \API_Exception("Failed to run permission check: Unrecognized target entity table ($entityTable)"); @@ -214,29 +221,26 @@ public function authorizeDelegate($action, $entityTable, $entityId, $apiRequest) throw new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity): Missing entity_id"); } - if ($this->isTrusted($apiRequest)) { - return; - } - /** * @var \Exception $exception */ $exception = NULL; $self = $this; \CRM_Core_Transaction::create(TRUE)->run(function($tx) use ($entity, $action, $entityId, &$exception, $self) { - $tx->rollback(); // Just to be safe. + // Just to be safe. + $tx->rollback(); - $params = array( + $params = [ 'version' => 3, 'check_permissions' => 1, 'id' => $entityId, - ); + ]; $result = $self->kernel->run($entity, $self->getDelegatedAction($action), $params); if ($result['is_error'] || empty($result['values'])) { - $exception = new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity,$entityId)", array( + $exception = new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity,$entityId)", [ 'cause' => $result, - )); + ]); } }); @@ -322,25 +326,25 @@ public function getDelegatedAction($action) { * @throws \Exception */ public function getDelegate($id) { - $query = \CRM_Core_DAO::executeQuery($this->lookupDelegateSql, array( - 1 => array($id, 'Positive'), - )); + $query = \CRM_Core_DAO::executeQuery($this->lookupDelegateSql, [ + 1 => [$id, 'Positive'], + ]); if ($query->fetch()) { if (!preg_match('/^civicrm_value_/', $query->entity_table)) { // A normal attachment directly on its entity. - return array($query->is_valid, $query->entity_table, $query->entity_id); + return [$query->is_valid, $query->entity_table, $query->entity_id]; } // Ex: Translate custom-field table ("civicrm_value_foo_4") to // entity table ("civicrm_activity"). - $tblIdx = \CRM_Utils_Array::index(array('table_name'), $this->getCustomFields()); + $tblIdx = \CRM_Utils_Array::index(['table_name'], $this->getCustomFields()); if (isset($tblIdx[$query->entity_table])) { - return array($query->is_valid, $tblIdx[$query->entity_table]['entity_table'], $query->entity_id); + return [$query->is_valid, $tblIdx[$query->entity_table]['entity_table'], $query->entity_id]; } throw new \Exception('Failed to lookup entity table for custom field.'); } else { - return array(FALSE, NULL, NULL); + return [FALSE, NULL, NULL]; } } @@ -360,14 +364,14 @@ public function isTrusted($apiRequest) { */ public function getCustomFields() { $query = \CRM_Core_DAO::executeQuery($this->lookupCustomFieldSql); - $rows = array(); + $rows = []; while ($query->fetch()) { - $rows[] = array( + $rows[] = [ 'field_name' => $query->field_name, 'table_name' => $query->table_name, 'extends' => $query->extends, 'entity_table' => \CRM_Core_BAO_CustomGroup::getTableNameByEntityName($query->extends), - ); + ]; } return $rows; } diff --git a/Civi/API/Subscriber/I18nSubscriber.php b/Civi/API/Subscriber/I18nSubscriber.php index 85f5de2142ff..335bc823afa5 100644 --- a/Civi/API/Subscriber/I18nSubscriber.php +++ b/Civi/API/Subscriber/I18nSubscriber.php @@ -1,9 +1,9 @@ array('onApiPrepare', Events::W_MIDDLE), - ); + return [ + Events::PREPARE => ['onApiPrepare', Events::W_MIDDLE], + Events::RESPOND => ['onApiRespond', Events::W_LATE], + ]; } /** + * Support multi-lingual requests + * * @param \Civi\API\Event\Event $event * API preparation event. * @@ -53,9 +64,33 @@ public static function getSubscribedEvents() { public function onApiPrepare(\Civi\API\Event\Event $event) { $apiRequest = $event->getApiRequest(); - // support multi-lingual requests - if ($language = \CRM_Utils_Array::value('option.language', $apiRequest['params'])) { - $this->setLocale($language); + $params = $apiRequest['params']; + if ($apiRequest['version'] < 4) { + $language = !empty($params['options']['language']) ? $params['options']['language'] : \CRM_Utils_Array::value('option.language', $params); + } + else { + $language = \CRM_Utils_Array::value('language', $params); + } + if ($language) { + $this->setLocale($language, $apiRequest['id']); + } + } + + /** + * Reset language to the default. + * + * @param \Civi\API\Event\Event $event + * + * @throws \API_Exception + */ + public function onApiRespond(\Civi\API\Event\Event $event) { + $apiRequest = $event->getApiRequest(); + + if (!empty($this->originalLang[$apiRequest['id']])) { + global $tsLocale; + global $dbLocale; + $tsLocale = $this->originalLang[$apiRequest['id']]['tsLocale']; + $dbLocale = $this->originalLang[$apiRequest['id']]['dbLocale']; } } @@ -63,44 +98,35 @@ public function onApiPrepare(\Civi\API\Event\Event $event) { * Sets the tsLocale and dbLocale for multi-lingual sites. * Some code duplication from CRM/Core/BAO/ConfigSetting.php retrieve() * to avoid regressions from refactoring. - * @param $lcMessagesRequest + * @param string $lcMessages + * @param int $requestId * @throws \API_Exception */ - public function setLocale($lcMessagesRequest) { - // We must validate whether the locale is valid, otherwise setting a bad - // dbLocale could probably lead to sql-injection. + public function setLocale($lcMessages, $requestId) { $domain = new \CRM_Core_DAO_Domain(); $domain->id = \CRM_Core_Config::domainID(); $domain->find(TRUE); - // are we in a multi-language setup? - $multiLang = $domain->locales ? TRUE : FALSE; - $lcMessages = NULL; - - // on multi-lang sites based on request and civicrm_uf_match - if ($multiLang) { - $config = \CRM_Core_Config::singleton(); - $languageLimit = array(); - if (isset($config->languageLimit) and $config->languageLimit) { - $languageLimit = $config->languageLimit; + // Check if the site is multi-lingual + if ($domain->locales && $lcMessages) { + // Validate language, otherwise a bad dbLocale could probably lead to sql-injection. + if (!array_key_exists($lcMessages, \Civi::settings()->get('languageLimit'))) { + throw new \API_Exception(ts('Language not enabled: %1', [1 => $lcMessages])); } - if (in_array($lcMessagesRequest, array_keys($languageLimit))) { - $lcMessages = $lcMessagesRequest; - } - else { - throw new \API_Exception(ts('Language not enabled: %1', array(1 => $lcMessagesRequest))); - } - } + global $dbLocale; + global $tsLocale; - global $dbLocale; + // Store original value to be restored in $this->onApiRespond + $this->originalLang[$requestId] = [ + 'tsLocale' => $tsLocale, + 'dbLocale' => $dbLocale, + ]; - // set suffix for table names - use views if more than one language - if ($lcMessages) { - $dbLocale = $multiLang && $lcMessages ? "_{$lcMessages}" : ''; + // Set suffix for table names - use views if more than one language + $dbLocale = "_{$lcMessages}"; - // FIXME: an ugly hack to fix CRM-4041 - global $tsLocale; + // Also set tsLocale - CRM-4041 $tsLocale = $lcMessages; } } diff --git a/Civi/API/Subscriber/PermissionCheck.php b/Civi/API/Subscriber/PermissionCheck.php index f9332727254e..c4fe5c748139 100644 --- a/Civi/API/Subscriber/PermissionCheck.php +++ b/Civi/API/Subscriber/PermissionCheck.php @@ -1,9 +1,9 @@ array( - array('onApiAuthorize', Events::W_LATE), - ), - ); + return [ + Events::AUTHORIZE => [ + ['onApiAuthorize', Events::W_LATE], + ], + ]; } /** @@ -122,8 +123,8 @@ public function checkACLPermission($apiRequest) { case 'ActionSchedule': $events = \CRM_Event_BAO_Event::getEvents(); $aclEdit = \CRM_ACL_API::group(\CRM_Core_Permission::EDIT, NULL, 'civicrm_event', $events); - $param = array('id' => $apiRequest['params']['id']); - $eventId = \CRM_Core_BAO_ActionSchedule::retrieve($param, $value = array()); + $param = ['id' => $apiRequest['params']['id']]; + $eventId = \CRM_Core_BAO_ActionSchedule::retrieve($param, $value = []); if (in_array($eventId->entity_value, $aclEdit)) { return TRUE; } diff --git a/Civi/API/Subscriber/TransactionSubscriber.php b/Civi/API/Subscriber/TransactionSubscriber.php index f33e742964d0..5f784320ca7f 100644 --- a/Civi/API/Subscriber/TransactionSubscriber.php +++ b/Civi/API/Subscriber/TransactionSubscriber.php @@ -1,9 +1,9 @@ array('onApiPrepare', Events::W_EARLY), - Events::RESPOND => array('onApiRespond', Events::W_MIDDLE), - Events::EXCEPTION => array('onApiException', Events::W_EARLY), - ); + return [ + Events::PREPARE => ['onApiPrepare', Events::W_EARLY], + Events::RESPOND => ['onApiRespond', Events::W_MIDDLE], + Events::EXCEPTION => ['onApiException', Events::W_EARLY], + ]; } /** * @var array (scalar $apiRequestId => CRM_Core_Transaction $tx) */ - private $transactions = array(); + private $transactions = []; /** * @var array (scalar $apiRequestId => bool) @@ -66,7 +67,7 @@ public static function getSubscribedEvents() { * A list of requests which should be forcibly rolled back to * their save points. */ - private $forceRollback = array(); + private $forceRollback = []; /** * Determine if an API request should be treated as transactional. @@ -78,6 +79,9 @@ public static function getSubscribedEvents() { * @return bool */ public function isTransactional($apiProvider, $apiRequest) { + if ($apiRequest['version'] == 4) { + return FALSE; + } if ($this->isForceRollback($apiProvider, $apiRequest)) { return TRUE; } @@ -97,6 +101,9 @@ public function isTransactional($apiProvider, $apiRequest) { * @return bool */ public function isForceRollback($apiProvider, $apiRequest) { + if ($apiRequest['version'] == 4) { + return FALSE; + } // FIXME: When APIv3 uses better parsing, only one check will be needed. if (isset($apiRequest['params']['options']['force_rollback'])) { return \CRM_Utils_String::strtobool($apiRequest['params']['options']['force_rollback']); diff --git a/Civi/API/Subscriber/WhitelistSubscriber.php b/Civi/API/Subscriber/WhitelistSubscriber.php index a32117b3e1d3..fbb272d3959d 100644 --- a/Civi/API/Subscriber/WhitelistSubscriber.php +++ b/Civi/API/Subscriber/WhitelistSubscriber.php @@ -1,9 +1,9 @@ array('onApiAuthorize', Events::W_EARLY), - Events::RESPOND => array('onApiRespond', Events::W_MIDDLE), - ); + return [ + Events::AUTHORIZE => ['onApiAuthorize', Events::W_EARLY], + Events::RESPOND => ['onApiRespond', Events::W_MIDDLE], + ]; } /** @@ -73,9 +72,9 @@ public static function getSubscribedEvents() { * @throws \CRM_Core_Exception */ public function __construct($rules) { - $this->rules = array(); + $this->rules = []; foreach ($rules as $rule) { - /** @var WhitelistRule $rule */ + /** @var \Civi\API\WhitelistRule $rule */ if ($rule->isValid()) { $this->rules[] = $rule; } @@ -89,7 +88,7 @@ public function __construct($rules) { * Determine which, if any, whitelist rules apply this request. * Reject unauthorized requests. * - * @param AuthorizeEvent $event + * @param \Civi\API\Event\AuthorizeEvent $event * @throws \CRM_Core_Exception */ public function onApiAuthorize(AuthorizeEvent $event) { @@ -108,7 +107,7 @@ public function onApiAuthorize(AuthorizeEvent $event) { /** * Apply any filtering rules based on the chosen whitelist rule. - * @param RespondEvent $event + * @param \Civi\API\Event\RespondEvent $event */ public function onApiRespond(RespondEvent $event) { $apiRequest = $event->getApiRequest(); diff --git a/Civi/API/Subscriber/WrapperAdapter.php b/Civi/API/Subscriber/WrapperAdapter.php index 9340b3b720eb..f2c3b5f98948 100644 --- a/Civi/API/Subscriber/WrapperAdapter.php +++ b/Civi/API/Subscriber/WrapperAdapter.php @@ -1,9 +1,9 @@ array('onApiPrepare', Events::W_MIDDLE), - Events::RESPOND => array('onApiRespond', Events::W_EARLY * 2), - ); + return [ + Events::PREPARE => ['onApiPrepare', Events::W_MIDDLE], + Events::RESPOND => ['onApiRespond', Events::W_EARLY * 2], + ]; } /** @@ -56,7 +56,7 @@ public static function getSubscribedEvents() { * @param array $defaults * array(\API_Wrapper). */ - public function __construct($defaults = array()) { + public function __construct($defaults = []) { $this->defaults = $defaults; } @@ -97,8 +97,8 @@ public function onApiRespond(\Civi\API\Event\RespondEvent $event) { * @return array<\API_Wrapper> */ public function getWrappers($apiRequest) { - if (!isset($apiRequest['wrappers'])) { - $apiRequest['wrappers'] = $this->defaults; + if (!isset($apiRequest['wrappers']) || is_null($apiRequest['wrappers'])) { + $apiRequest['wrappers'] = $apiRequest['version'] < 4 ? $this->defaults : []; \CRM_Utils_Hook::apiWrappers($apiRequest['wrappers'], $apiRequest); } return $apiRequest['wrappers']; diff --git a/Civi/API/Subscriber/XDebugSubscriber.php b/Civi/API/Subscriber/XDebugSubscriber.php index 7eab69c7beb8..0648cf50b73b 100644 --- a/Civi/API/Subscriber/XDebugSubscriber.php +++ b/Civi/API/Subscriber/XDebugSubscriber.php @@ -1,9 +1,9 @@ array('onApiRespond', Events::W_LATE), - ); + return [ + Events::RESPOND => ['onApiRespond', Events::W_LATE], + ]; } /** diff --git a/Civi/API/WhitelistRule.php b/Civi/API/WhitelistRule.php index c18c712a936b..0cc9ef01292e 100644 --- a/Civi/API/WhitelistRule.php +++ b/Civi/API/WhitelistRule.php @@ -1,9 +1,9 @@ actions = '*'; } else { - $this->actions = array(); + $this->actions = []; foreach ((array) $ruleSpec['actions'] as $action) { $this->actions[] = Request::normalizeActionName($action, $ruleSpec['version']); } @@ -197,7 +197,7 @@ public function matches($apiRequest) { // Kind'a silly we need to (re(re))parse here for each rule; would be more // performant if pre-parsed by Request::create(). $options = _civicrm_api3_get_options_from_params($apiRequest['params'], TRUE, $apiRequest['entity'], 'get'); - $return = \CRM_Utils_Array::value('return', $options, array()); + $return = \CRM_Utils_Array::value('return', $options, []); $activatedFields = array_merge($activatedFields, array_keys($return)); } @@ -267,7 +267,7 @@ public function filter($apiRequest, $apiResult) { * List of acceptable keys. */ protected function filterFields($keys) { - $r = array(); + $r = []; foreach ($keys as $key) { if (in_array($key, $this->fields)) { $r[] = $key; diff --git a/Civi/ActionSchedule/Event/MailingQueryEvent.php b/Civi/ActionSchedule/Event/MailingQueryEvent.php index 472c1c3bcdd4..e8b136e84dc4 100644 --- a/Civi/ActionSchedule/Event/MailingQueryEvent.php +++ b/Civi/ActionSchedule/Event/MailingQueryEvent.php @@ -1,7 +1,6 @@ Mapping $mapping). */ - protected $mappings = array(); + protected $mappings = []; /** * Register a new mapping. * - * @param MappingInterface $mapping + * @param \Civi\ActionSchedule\MappingInterface $mapping * The new mapping. * @return MappingRegisterEvent */ diff --git a/Civi/ActionSchedule/Mapping.php b/Civi/ActionSchedule/Mapping.php index dd25ee7af380..d6a97eb1935c 100644 --- a/Civi/ActionSchedule/Mapping.php +++ b/Civi/ActionSchedule/Mapping.php @@ -1,9 +1,9 @@ entity_status); @@ -242,7 +242,7 @@ public function getStatusLabels($value) { * Array(string $fieldName => string $fieldLabel). */ public function getDateFields() { - $dateFieldLabels = array(); + $dateFieldLabels = []; if (!empty($this->entity_date_start)) { $dateFieldLabels[$this->entity_date_start] = ucwords(str_replace('_', ' ', $this->entity_date_start)); } @@ -263,7 +263,7 @@ public function getDateFields() { * Ex: array('assignee' => 'Activity Assignee'). */ public function getRecipientTypes() { - return array(); + return []; } /** @@ -280,7 +280,7 @@ public function getRecipientTypes() { * @see getRecipientTypes */ public function getRecipientListing($recipientType) { - return array(); + return []; } protected static function getValueLabelMap($name) { @@ -300,11 +300,11 @@ protected static function getValueLabelMap($name) { $valueLabelMap['civicrm_membership_type'] = \CRM_Member_PseudoConstant::membershipType(); $allCustomFields = \CRM_Core_BAO_CustomField::getFields(''); - $dateFields = array( + $dateFields = [ 'birth_date' => ts('Birth Date'), 'created_date' => ts('Created Date'), 'modified_date' => ts('Modified Date'), - ); + ]; foreach ($allCustomFields as $fieldID => $field) { if ($field['data_type'] == 'Date') { $dateFields["custom_$fieldID"] = $field['label']; @@ -326,7 +326,7 @@ protected static function getValueLabelMap($name) { * List of error messages. */ public function validateSchedule($schedule) { - return array(); + return []; } /** @@ -339,6 +339,18 @@ public function validateSchedule($schedule) { * @param array $defaultParams * @return \CRM_Utils_SQL_Select */ - public abstract function createQuery($schedule, $phase, $defaultParams); + abstract public function createQuery($schedule, $phase, $defaultParams); + + /** + * Determine whether a schedule based on this mapping should + * reset the reminder state if the trigger date changes. + * + * @return bool + * + * @param \CRM_Core_DAO_ActionSchedule $schedule + */ + public function resetOnTriggerDateChange($schedule) { + return FALSE; + } } diff --git a/Civi/ActionSchedule/MappingInterface.php b/Civi/ActionSchedule/MappingInterface.php index 6dc726dc8a94..137a25ef9b15 100644 --- a/Civi/ActionSchedule/MappingInterface.php +++ b/Civi/ActionSchedule/MappingInterface.php @@ -1,9 +1,9 @@ prepareQuery(self::PHASE_RELATION_FIRST); $startDateClauses = $this->prepareStartDateClauses(); - - // In some cases reference_date got outdated due to many reason e.g. In Membership renewal end_date got extended - // which means reference date mismatches with the end_date where end_date may be used as the start_action_date - // criteria for some schedule reminder so in order to send new reminder we INSERT new reminder with new reference_date - // value via UNION operation - $referenceReminderIDs = array(); - $referenceDate = NULL; - if (!empty($query['casUseReferenceDate'])) { - // First retrieve all the action log's ids which are outdated or in other words reference_date now don't match with entity date. - // And the retrieve the updated entity date which will later used below to update all other outdated action log records - $sql = $query->copy() - ->select('reminder.id as id') - ->select($query['casDateField'] . ' as reference_date') - ->merge($this->joinReminder('INNER JOIN', 'rel', $query)) - ->where("reminder.id IS NOT NULL AND reminder.reference_date IS NOT NULL AND reminder.reference_date <> !casDateField") - ->where($startDateClauses) - ->orderBy("reminder.id desc") - ->strict() - ->toSQL(); - $dao = \CRM_Core_DAO::executeQuery($sql); - - while ($dao->fetch()) { - $referenceReminderIDs[] = $dao->id; - $referenceDate = $dao->reference_date; - } - } - - if (empty($referenceReminderIDs)) { - $firstQuery = $query->copy() - ->merge($this->selectIntoActionLog(self::PHASE_RELATION_FIRST, $query)) - ->merge($this->joinReminder('LEFT JOIN', 'rel', $query)) - ->where("reminder.id IS NULL") - ->where($startDateClauses) - ->strict() - ->toSQL(); - \CRM_Core_DAO::executeQuery($firstQuery); - } - else { - // INSERT new log to send reminder as desired entity date got updated - $referenceQuery = $query->copy() - ->merge($this->selectIntoActionLog(self::PHASE_RELATION_FIRST, $query)) - ->merge($this->joinReminder('LEFT JOIN', 'rel', $query)) - ->where("reminder.id = !reminderID") - ->where($startDateClauses) - ->param('reminderID', $referenceReminderIDs[0]) - ->strict() - ->toSQL(); - \CRM_Core_DAO::executeQuery($referenceQuery); - - // Update all the previous outdated reference date valued, action_log rows to the latest changed entity date - $updateQuery = "UPDATE civicrm_action_log SET reference_date = '" . $referenceDate . "' WHERE id IN (" . implode(', ', $referenceReminderIDs) . ")"; - \CRM_Core_DAO::executeQuery($updateQuery); - } + // Send reminder to all contacts who have never received this scheduled reminder + $firstInstanceQuery = $query->copy() + ->merge($this->selectIntoActionLog(self::PHASE_RELATION_FIRST, $query)) + ->merge($this->joinReminder('LEFT JOIN', 'rel', $query)) + ->where("reminder.id IS NULL") + ->where($startDateClauses) + ->strict() + ->toSQL(); + \CRM_Core_DAO::executeQuery($firstInstanceQuery); } /** @@ -241,7 +198,7 @@ protected function buildAddlFirstPass() { $query = $this->prepareQuery(self::PHASE_ADDITION_FIRST); $insertAdditionalSql = \CRM_Utils_SQL_Select::from("civicrm_contact c") - ->merge($query, array('params')) + ->merge($query, ['params']) ->merge($this->selectIntoActionLog(self::PHASE_ADDITION_FIRST, $query)) ->merge($this->joinReminder('LEFT JOIN', 'addl', $query)) ->where('reminder.id IS NULL') @@ -276,31 +233,18 @@ protected function buildRelRepeatPass() { // @todo - this only handles events that get moved later. Potentially they might get moved earlier $repeatInsert = $query ->merge($this->joinReminder('INNER JOIN', 'rel', $query)) - ->merge($this->selectActionLogFields(self::PHASE_RELATION_REPEAT, $query)) - ->select("MAX(reminder.action_date_time) as latest_log_time") + ->merge($this->selectIntoActionLog(self::PHASE_RELATION_REPEAT, $query)) ->merge($this->prepareRepetitionEndFilter($query['casDateField'])) - ->where($this->actionSchedule->start_action_date ? $startDateClauses[0] : array()) + ->where($this->actionSchedule->start_action_date ? $startDateClauses[0] : []) ->groupBy("reminder.contact_id, reminder.entity_id, reminder.entity_table") - // @todo replace use of timestampdiff with a direct comparison as TIMESTAMPDIFF cannot use an index. - ->having("TIMESTAMPDIFF(HOUR, latest_log_time, CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, latest_log_time, DATE_ADD(latest_log_time, INTERVAL !casRepetitionInterval))") - ->param(array( + ->having("TIMESTAMPDIFF(HOUR, MAX(reminder.action_date_time), CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, MAX(reminder.action_date_time), DATE_ADD(MAX(reminder.action_date_time), INTERVAL !casRepetitionInterval))") + ->param([ 'casRepetitionInterval' => $this->parseRepetitionInterval(), - )) + ]) ->strict() ->toSQL(); - // For unknown reasons, we manually insert each row. Why not change - // selectActionLogFields() to selectIntoActionLog() above? - - $arrValues = \CRM_Core_DAO::executeQuery($repeatInsert)->fetchAll(); - if ($arrValues) { - \CRM_Core_DAO::executeQuery( - \CRM_Utils_SQL_Insert::into('civicrm_action_log') - ->columns(array('contact_id', 'entity_id', 'entity_table', 'action_schedule_id')) - ->rows($arrValues) - ->toSQL() - ); - } + \CRM_Core_DAO::executeQuery($repeatInsert); } /** @@ -314,7 +258,7 @@ protected function buildAddlRepeatPass() { $addlCheck = \CRM_Utils_SQL_Select::from($query['casAddlCheckFrom']) ->select('*') - ->merge($query, array('wheres'))// why only where? why not the joins? + ->merge($query, ['params', 'wheres', 'joins']) ->merge($this->prepareRepetitionEndFilter($query['casDateField'])) ->limit(1) ->strict() @@ -323,32 +267,19 @@ protected function buildAddlRepeatPass() { $daoCheck = \CRM_Core_DAO::executeQuery($addlCheck); if ($daoCheck->fetch()) { $repeatInsertAddl = \CRM_Utils_SQL_Select::from('civicrm_contact c') - ->merge($this->selectActionLogFields(self::PHASE_ADDITION_REPEAT, $query)) + ->merge($this->selectIntoActionLog(self::PHASE_ADDITION_REPEAT, $query)) ->merge($this->joinReminder('INNER JOIN', 'addl', $query)) - ->select("MAX(reminder.action_date_time) as latest_log_time") - ->merge($this->prepareAddlFilter('c.id')) + ->merge($this->prepareAddlFilter('c.id'), ['params']) ->where("c.is_deleted = 0 AND c.is_deceased = 0") ->groupBy("reminder.contact_id") - // @todo replace use of timestampdiff with a direct comparison as TIMESTAMPDIFF cannot use an index. - ->having("TIMESTAMPDIFF(HOUR, latest_log_time, CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, latest_log_time, DATE_ADD(latest_log_time, INTERVAL !casRepetitionInterval)") - ->param(array( + ->having("TIMESTAMPDIFF(HOUR, MAX(reminder.action_date_time), CAST(!casNow AS datetime)) >= TIMESTAMPDIFF(HOUR, MAX(reminder.action_date_time), DATE_ADD(MAX(reminder.action_date_time), INTERVAL !casRepetitionInterval))") + ->param([ 'casRepetitionInterval' => $this->parseRepetitionInterval(), - )) + ]) ->strict() ->toSQL(); - // For unknown reasons, we manually insert each row. Why not change - // selectActionLogFields() to selectIntoActionLog() above? - - $addValues = \CRM_Core_DAO::executeQuery($repeatInsertAddl)->fetchAll(); - if ($addValues) { - \CRM_Core_DAO::executeQuery( - \CRM_Utils_SQL_Insert::into('civicrm_action_log') - ->columns(array('contact_id', 'entity_id', 'entity_table', 'action_schedule_id')) - ->rows($addValues) - ->toSQL() - ); - } + \CRM_Core_DAO::executeQuery($repeatInsertAddl); } } @@ -358,12 +289,12 @@ protected function buildAddlRepeatPass() { * @throws \CRM_Core_Exception */ protected function prepareQuery($phase) { - $defaultParams = array( + $defaultParams = [ 'casActionScheduleId' => $this->actionSchedule->id, 'casMappingId' => $this->mapping->getId(), 'casMappingEntity' => $this->mapping->getEntity(), 'casNow' => $this->now, - ); + ]; /** @var \CRM_Utils_SQL_Select $query */ $query = $this->mapping->createQuery($this->actionSchedule, $phase, $defaultParams); @@ -419,18 +350,31 @@ protected function prepareContactFilter($contactIdField) { $actionSchedule = $this->actionSchedule; if ($actionSchedule->group_id) { - if ($this->isSmartGroup($actionSchedule->group_id)) { - // Check that the group is in place in the cache and up to date - \CRM_Contact_BAO_GroupContactCache::check($actionSchedule->group_id); - return \CRM_Utils_SQL_Select::fragment() - ->join('grp', "INNER JOIN civicrm_group_contact_cache grp ON {$contactIdField} = grp.contact_id") - ->where(" grp.group_id IN ({$actionSchedule->group_id})"); + $regularGroupIDs = $smartGroupIDs = $groupWhereCLause = []; + $query = \CRM_Utils_SQL_Select::fragment(); + + // get child group IDs if any + $childGroupIDs = \CRM_Contact_BAO_Group::getChildGroupIds($actionSchedule->group_id); + foreach (array_merge([$actionSchedule->group_id], $childGroupIDs) as $groupID) { + if ($this->isSmartGroup($groupID)) { + // Check that the group is in place in the cache and up to date + \CRM_Contact_BAO_GroupContactCache::check($groupID); + $smartGroupIDs[] = $groupID; + } + else { + $regularGroupIDs[] = $groupID; + } } - else { - return \CRM_Utils_SQL_Select::fragment() - ->join('grp', " INNER JOIN civicrm_group_contact grp ON {$contactIdField} = grp.contact_id AND grp.status = 'Added'") - ->where(" grp.group_id IN ({$actionSchedule->group_id})"); + + if (!empty($smartGroupIDs)) { + $query->join('sg', "LEFT JOIN civicrm_group_contact_cache sg ON {$contactIdField} = sg.contact_id"); + $groupWhereCLause[] = " sg.group_id IN ( " . implode(', ', $smartGroupIDs) . " ) "; + } + if (!empty($regularGroupIDs)) { + $query->join('rg', " LEFT JOIN civicrm_group_contact rg ON {$contactIdField} = rg.contact_id AND rg.status = 'Added'"); + $groupWhereCLause[] = " rg.group_id IN ( " . implode(', ', $regularGroupIDs) . " ) "; } + return $query->where(implode(" OR ", $groupWhereCLause)); } elseif (!empty($actionSchedule->recipient_manual)) { $rList = \CRM_Utils_Type::escape($actionSchedule->recipient_manual, 'String'); @@ -468,7 +412,7 @@ protected function prepareLanguageFilter($contactTableAlias) { */ protected function prepareStartDateClauses() { $actionSchedule = $this->actionSchedule; - $startDateClauses = array(); + $startDateClauses = []; if ($actionSchedule->start_action_date) { $op = ($actionSchedule->start_action_condition == 'before' ? '<=' : '>='); $operator = ($actionSchedule->start_action_condition == 'before' ? 'DATE_SUB' : 'DATE_ADD'); @@ -523,9 +467,9 @@ protected function prepareRepetitionEndFilter($dateField) { return \CRM_Utils_SQL_Select::fragment() ->where("@casNow <= !repetitionEndDate") - ->param(array( + ->param([ '!repetitionEndDate' => $repeatEventDateExpr, - )); + ]); } /** @@ -552,40 +496,42 @@ protected function prepareAddlFilter($contactIdField) { * @throws \CRM_Core_Exception */ protected function selectActionLogFields($phase, $query) { + $selectArray = []; switch ($phase) { case self::PHASE_RELATION_FIRST: case self::PHASE_RELATION_REPEAT: $fragment = \CRM_Utils_SQL_Select::fragment(); - // CRM-15376: We are not tracking the reference date for 'repeated' schedule reminders. - if (!empty($query['casUseReferenceDate'])) { - $fragment->select($query['casDateField']); + $selectArray = [ + "!casContactIdField as contact_id", + "!casEntityIdField as entity_id", + "@casMappingEntity as entity_table", + "#casActionScheduleId as action_schedule_id", + ]; + if ($this->resetOnTriggerDateChange()) { + $selectArray[] = "!casDateField as reference_date"; } - $fragment->select( - array( - "!casContactIdField as contact_id", - "!casEntityIdField as entity_id", - "@casMappingEntity as entity_table", - "#casActionScheduleId as action_schedule_id", - ) - ); break; case self::PHASE_ADDITION_FIRST: case self::PHASE_ADDITION_REPEAT: - $fragment = \CRM_Utils_SQL_Select::fragment(); - $fragment->select( - array( - "c.id as contact_id", - "c.id as entity_id", - "'civicrm_contact' as entity_table", - "#casActionScheduleId as action_schedule_id", - ) - ); + //CRM-19017: Load default params for fragment query object. + $params = [ + 'casActionScheduleId' => $this->actionSchedule->id, + 'casNow' => $this->now, + ]; + $fragment = \CRM_Utils_SQL_Select::fragment()->param($params); + $selectArray = [ + "c.id as contact_id", + "c.id as entity_id", + "'civicrm_contact' as entity_table", + "#casActionScheduleId as action_schedule_id", + ]; break; default: throw new \CRM_Core_Exception("Unrecognized phase: $phase"); } + $fragment->select($selectArray); return $fragment; } @@ -601,16 +547,15 @@ protected function selectActionLogFields($phase, $query) { * @throws \CRM_Core_Exception */ protected function selectIntoActionLog($phase, $query) { - $actionLogColumns = array( + $actionLogColumns = [ "contact_id", "entity_id", "entity_table", "action_schedule_id", - ); - if ($phase === self::PHASE_RELATION_FIRST || $phase === self::PHASE_RELATION_REPEAT) { - if (!empty($query['casUseReferenceDate'])) { - array_unshift($actionLogColumns, 'reference_date'); - } + ]; + + if ($this->resetOnTriggerDateChange() && ($phase == self::PHASE_RELATION_FIRST || $phase == self::PHASE_RELATION_REPEAT)) { + $actionLogColumns[] = "reference_date"; } return $this->selectActionLogFields($phase, $query) @@ -651,6 +596,10 @@ protected function joinReminder($joinType, $for, $query) { reminder.entity_table = '{$entityName}' AND reminder.action_schedule_id = {$this->actionSchedule->id}"; + if ($for == 'rel' && $this->resetOnTriggerDateChange()) { + $joinClause .= " AND\nreminder.reference_date = !casDateField"; + } + // Why do we only include anniversary clause for 'rel' queries? if ($for === 'rel' && !empty($query['casAnniversaryMode'])) { // only consider reminders less than 11 months ago @@ -660,4 +609,14 @@ protected function joinReminder($joinType, $for, $query) { return \CRM_Utils_SQL_Select::fragment()->join("reminder", "$joinType $joinClause"); } + /** + * Should we use the reference date when checking to see if we already + * sent reminders. + * + * @return bool + */ + protected function resetOnTriggerDateChange() { + return $this->mapping->resetOnTriggerDateChange($this->actionSchedule); + } + } diff --git a/Civi/Angular/AngularLoader.php b/Civi/Angular/AngularLoader.php new file mode 100644 index 000000000000..f74479fb4685 --- /dev/null +++ b/Civi/Angular/AngularLoader.php @@ -0,0 +1,321 @@ +

    ` or `angular.bootstrap(...)`. + * + * @code + * $loader = new AngularLoader(); + * $loader->setPageName('civicrm/case/a'); + * $loader->setModules(array('crmApp')); + * $loader->load(); + * @endCode + * + * @link https://docs.angularjs.org/guide/bootstrap + */ +class AngularLoader { + + /** + * The weight to assign to any Angular JS module files. + */ + const DEFAULT_MODULE_WEIGHT = 200; + + /** + * The resource manager. + * + * Do not use publicly. Inject your own copy! + * + * @var \CRM_Core_Resources + */ + protected $res; + + /** + * The Angular module manager. + * + * Do not use publicly. Inject your own copy! + * + * @var \Civi\Angular\Manager + */ + protected $angular; + + /** + * The region of the page into which JavaScript will be loaded. + * + * @var string + */ + protected $region; + + /** + * @var string + * Ex: 'civicrm/a'. + */ + protected $pageName; + + /** + * @var array + * A list of modules to load. + */ + protected $modules; + + /** + * @var array|null + */ + protected $crmApp = NULL; + + /** + * AngularLoader constructor. + */ + public function __construct() { + $this->res = \CRM_Core_Resources::singleton(); + $this->angular = \Civi::service('angular'); + $this->region = \CRM_Utils_Request::retrieve('snippet', 'String') ? 'ajax-snippet' : 'html-header'; + $this->pageName = isset($_GET['q']) ? $_GET['q'] : NULL; + $this->modules = []; + } + + /** + * Register resources required by Angular. + * + * @return AngularLoader + */ + public function load() { + $angular = $this->getAngular(); + $res = $this->getRes(); + + if ($this->crmApp !== NULL) { + $this->addModules($this->crmApp['modules']); + $region = \CRM_Core_Region::instance($this->crmApp['region']); + $region->update('default', ['disabled' => TRUE]); + $region->add(['template' => $this->crmApp['file'], 'weight' => 0]); + $this->res->addSetting([ + 'crmApp' => [ + 'defaultRoute' => $this->crmApp['defaultRoute'], + ], + ]); + + // If trying to load an Angular page via AJAX, the route must be passed as a + // URL parameter, since the server doesn't receive information about + // URL fragments (i.e, what comes after the #). + $this->res->addSetting([ + 'angularRoute' => $this->crmApp['activeRoute'], + ]); + } + + $moduleNames = $this->findActiveModules(); + if (!$this->isAllModules($moduleNames)) { + $assetParams = ['modules' => implode(',', $moduleNames)]; + } + else { + // The module list will be "all modules that the user can see". + $assetParams = ['nonce' => md5(implode(',', $moduleNames))]; + } + + $res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams) { + // TODO optimization; client-side caching + $result = array_merge($angular->getResources($moduleNames, 'settings', 'settings'), [ + 'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), + 'angular' => [ + 'modules' => $moduleNames, + 'requires' => $angular->getResources($moduleNames, 'requires', 'requires'), + 'cacheCode' => $res->getCacheCode(), + 'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams), + ], + ]); + return $result; + }); + + $res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE); + $res->addScriptFile('civicrm', 'js/crm.angular.js', 101, $this->getRegion(), FALSE); + + $headOffset = 0; + $config = \CRM_Core_Config::singleton(); + if ($config->debug) { + foreach ($moduleNames as $moduleName) { + foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { + $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + } + foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { + $res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + // addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + } + } + } + else { + // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + // $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); + $aggScriptUrl = \Civi::service('asset_builder')->getUrl('angular-modules.js', $assetParams); + $res->addScriptUrl($aggScriptUrl, 120, $this->getRegion()); + + // FIXME: The following CSS aggregator doesn't currently handle path-adjustments - which can break icons. + //$aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); + //$aggStyleUrl = \Civi::service('asset_builder')->getUrl('angular-modules.css', $assetParams); + //$res->addStyleUrl($aggStyleUrl, 120, $this->getRegion()); + + foreach ($this->angular->getResources($moduleNames, 'css', 'cacheUrl') as $url) { + $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + } + } + + return $this; + } + + /** + * Use Civi's generic "application" module. + * + * This is suitable for use on a basic, standalone Angular page + * like `civicrm/a`. (If you need to integrate Angular with pre-existing, + * non-Angular pages... then this probably won't help.) + * + * The Angular bootstrap process requires an HTML directive like + * `
    `. + * + * Calling useApp() will replace the page's main body with the + * `
    ...
    ` and apply some configuration options + * for the `crmApp` module. + * + * @param array $settings + * A list of settings. Accepted values: + * - activeRoute: string, the route to open up immediately + * Ex: '/case/list' + * - defaultRoute: string, use this to redirect the default route (`/`) to another page + * Ex: '/case/list' + * - region: string, the place on the page where we should insert the angular app + * Ex: 'page-body' + * @return AngularLoader + * @link https://code.angularjs.org/1.5.11/docs/guide/bootstrap + */ + public function useApp($settings = []) { + $defaults = [ + 'modules' => ['crmApp'], + 'activeRoute' => NULL, + 'defaultRoute' => NULL, + 'region' => 'page-body', + 'file' => 'Civi/Angular/Page/Main.tpl', + ]; + $this->crmApp = array_merge($defaults, $settings); + return $this; + } + + /** + * Get a list of all Angular modules which should be activated on this + * page. + * + * @return array + * List of module names. + * Ex: array('angularFileUpload', 'crmUi', 'crmUtil'). + */ + public function findActiveModules() { + return $this->angular->resolveDependencies(array_merge( + $this->getModules(), + $this->angular->resolveDefaultModules($this->getPageName()) + )); + } + + /** + * @param $moduleNames + * @return int + */ + private function isAllModules($moduleNames) { + $allModuleNames = array_keys($this->angular->getModules()); + return count(array_diff($allModuleNames, $moduleNames)) === 0; + } + + /** + * @return \CRM_Core_Resources + */ + public function getRes() { + return $this->res; + } + + /** + * @param \CRM_Core_Resources $res + * @return AngularLoader + */ + public function setRes($res) { + $this->res = $res; + return $this; + } + + /** + * @return \Civi\Angular\Manager + */ + public function getAngular() { + return $this->angular; + } + + /** + * @param \Civi\Angular\Manager $angular + * @return AngularLoader + */ + public function setAngular($angular) { + $this->angular = $angular; + return $this; + } + + /** + * @return string + */ + public function getRegion() { + return $this->region; + } + + /** + * @param string $region + * @return AngularLoader + */ + public function setRegion($region) { + $this->region = $region; + return $this; + } + + /** + * @return string + * Ex: 'civicrm/a'. + */ + public function getPageName() { + return $this->pageName; + } + + /** + * @param string $pageName + * Ex: 'civicrm/a'. + * @return AngularLoader + */ + public function setPageName($pageName) { + $this->pageName = $pageName; + return $this; + } + + /** + * @param array|string $modules + * @return AngularLoader + */ + public function addModules($modules) { + $modules = (array) $modules; + $this->modules = array_unique(array_merge($this->modules, $modules)); + return $this; + } + + /** + * @return array + */ + public function getModules() { + return $this->modules; + } + + /** + * @param array $modules + * @return AngularLoader + */ + public function setModules($modules) { + $this->modules = $modules; + return $this; + } + +} diff --git a/Civi/Angular/ChangeSet.php b/Civi/Angular/ChangeSet.php new file mode 100644 index 000000000000..27c92a5867b1 --- /dev/null +++ b/Civi/Angular/ChangeSet.php @@ -0,0 +1,188 @@ +resFilters as $filter) { + if ($filter['resourceType'] === $resourceType) { + $resources = call_user_func($filter['callback'], $resources); + } + } + } + return $resources; + } + + /** + * Update a set of HTML snippets. + * + * @param array $changeSets + * Array(ChangeSet). + * @param array $strings + * Array(string $path => string $html). + * @return array + * Updated list of $strings. + * @throws \CRM_Core_Exception + */ + private static function applyHtmlFilters($changeSets, $strings) { + $coder = new Coder(); + + foreach ($strings as $path => $html) { + /** @var \phpQueryObject $doc */ + $doc = NULL; + + // Most docs don't need phpQueryObject. Initialize phpQuery on first match. + + foreach ($changeSets as $changeSet) { + /** @var ChangeSet $changeSet */ + foreach ($changeSet->htmlFilters as $filter) { + if (preg_match($filter['regex'], $path)) { + if ($doc === NULL) { + $doc = \phpQuery::newDocument($html, 'text/html'); + } + call_user_func($filter['callback'], $doc, $path); + } + } + } + + if ($doc !== NULL) { + $strings[$path] = $coder->encode($doc); + } + } + return $strings; + } + + /** + * @var string + */ + protected $name; + + /** + * @var array + * Each item is an array with keys: + * - resourceType: string + * - callback: function + */ + protected $resFilters = []; + + /** + * @var array + * Each item is an array with keys: + * - regex: string + * - callback: function + */ + protected $htmlFilters = []; + + /** + * @param string $name + * Symbolic name for this changeset. + * @return \Civi\Angular\ChangeSetInterface + */ + public static function create($name) { + $changeSet = new ChangeSet(); + $changeSet->name = $name; + return $changeSet; + } + + /** + * Declare that $module requires additional dependencies. + * + * @param string $module + * @param string|array $dependencies + * @return ChangeSet + */ + public function requires($module, $dependencies) { + $dependencies = (array) $dependencies; + return $this->alterResource('requires', + function ($values) use ($module, $dependencies) { + if (!isset($values[$module])) { + $values[$module] = []; + } + $values[$module] = array_unique(array_merge($values[$module], $dependencies)); + return $values; + }); + } + + /** + * Declare a change to a resource. + * + * @param string $resourceType + * @param callable $callback + * @return ChangeSet + */ + public function alterResource($resourceType, $callback) { + $this->resFilters[] = [ + 'resourceType' => $resourceType, + 'callback' => $callback, + ]; + return $this; + } + + /** + * Declare a change to HTML. + * + * @param string $file + * A file name, wildcard, or regex. + * Ex: '~/crmHello/intro.html' (filename) + * Ex: '~/crmHello/*.html' (wildcard) + * Ex: ';(Edit|List)Ctrl\.html$;' (regex) + * @param callable $callback + * Function which accepts up to two parameters: + * - phpQueryObject $doc + * - string $path + * @return ChangeSet + */ + public function alterHtml($file, $callback) { + $this->htmlFilters[] = [ + 'regex' => ($file{0} === ';') ? $file : $this->createRegex($file), + 'callback' => $callback, + ]; + return $this; + } + + /** + * Convert a string with a wildcard (*) to a regex. + * + * @param string $filterExpr + * Ex: "/foo/*.bar" + * @return string + * Ex: ";^/foo/[^/]*\.bar$;" + */ + protected function createRegex($filterExpr) { + $regex = preg_quote($filterExpr, ';'); + $regex = str_replace('\\*', '[^/]*', $regex); + $regex = ";^$regex$;"; + return $regex; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) { + $this->name = $name; + } + +} diff --git a/Civi/Angular/ChangeSetInterface.php b/Civi/Angular/ChangeSetInterface.php new file mode 100644 index 000000000000..9f30447e374f --- /dev/null +++ b/Civi/Angular/ChangeSetInterface.php @@ -0,0 +1,38 @@ +recode($html); + } + catch (\Exception $e) { + return FALSE; + } + + $htmlSig = preg_replace('/[ \t\r\n\/]+/', '', $this->cleanup($html)); + $docSig = preg_replace('/[ \t\r\n\/]+/', '', $recodedHtml); + if ($htmlSig !== $docSig || empty($html) != empty($htmlSig)) { + return FALSE; + } + return TRUE; + } + + /** + * Parse an HTML snippet and re-encode is as HTML. + * + * This is useful for detecting cases where the parser or encoder + * have quirks/bugs. + * + * @param string $html + * @return string + */ + public function recode($html) { + $doc = \phpQuery::newDocument("$html", 'text/html'); + return $this->encode($doc); + } + + /** + * Encode a phpQueryObject as HTML. + * + * @param \phpQueryObject $doc + * @return string + * HTML + */ + public function encode($doc) { + $doc->document->formatOutput = TRUE; + return $this->cleanup($doc->markupOuter()); + } + + protected function cleanup($html) { + $html = preg_replace_callback("/([\\-a-zA-Z0-9]+)=(')([^']*)(')/", [$this, 'cleanupAttribute'], $html); + $html = preg_replace_callback('/([\-a-zA-Z0-9]+)=(")([^"]*)(")/', [$this, 'cleanupAttribute'], $html); + return $html; + } + + protected function cleanupAttribute($matches) { + list ($full, $attr, $lquote, $value, $rquote) = $matches; + + switch ($attr) { + case 'href': + if (strpos($value, '%7B%7B') !== FALSE && strpos($value, '%7D%7D') !== FALSE) { + $value = urldecode($value); + } + break; + + default: + $value = html_entity_decode($value); + break; + } + + return "$attr=$lquote$value$rquote"; + } + +} diff --git a/Civi/Angular/Manager.php b/Civi/Angular/Manager.php index 9982b21c6b83..cb4c4c86d27f 100644 --- a/Civi/Angular/Manager.php +++ b/Civi/Angular/Manager.php @@ -14,7 +14,9 @@ class Manager { protected $res = NULL; /** - * @var array|NULL + * Modules. + * + * @var array|null * Each item has some combination of these keys: * - ext: string * The Civi extension which defines the Angular module. @@ -30,12 +32,25 @@ class Manager { */ protected $modules = NULL; + /** + * @var \CRM_Utils_Cache_Interface + */ + protected $cache; + + /** + * @var array + * Array(string $name => ChangeSet $change). + */ + protected $changeSets = NULL; + /** * @param \CRM_Core_Resources $res * The resource manager. + * @param $cache */ - public function __construct($res) { + public function __construct($res, \CRM_Utils_Cache_Interface $cache = NULL) { $this->res = $res; + $this->cache = $cache ? $cache : new \CRM_Utils_Cache_ArrayCache([]); } /** @@ -58,92 +73,43 @@ public function __construct($res) { public function getModules() { if ($this->modules === NULL) { $config = \CRM_Core_Config::singleton(); + global $civicrm_root; + + // Note: It would be nice to just glob("$civicrm_root/ang/*.ang.php"), but at time + // of writing CiviMail and CiviCase have special conditionals. - $angularModules = array(); - $angularModules['angularFileUpload'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-file-upload/angular-file-upload.min.js'), - ); - $angularModules['crmApp'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmApp.js'), - ); - $angularModules['crmAttachment'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmAttachment.js'), - 'css' => array('ang/crmAttachment.css'), - 'partials' => array('ang/crmAttachment'), - 'settings' => array( - 'token' => \CRM_Core_Page_AJAX_Attachment::createToken(), - ), - ); - $angularModules['crmAutosave'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmAutosave.js'), - ); - $angularModules['crmCxn'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmCxn.js', 'ang/crmCxn/*.js'), - 'css' => array('ang/crmCxn.css'), - 'partials' => array('ang/crmCxn'), - ); - //$angularModules['crmExample'] = array( - // 'ext' => 'civicrm', - // 'js' => array('ang/crmExample.js'), - // 'partials' => array('ang/crmExample'), - //); - $angularModules['crmResource'] = array( - 'ext' => 'civicrm', - // 'js' => array('js/angular-crmResource/byModule.js'), // One HTTP request per module. - 'js' => array('js/angular-crmResource/all.js'), // One HTTP request for all modules. - ); - $angularModules['crmUi'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmUi.js'), - 'partials' => array('ang/crmUi'), - ); - $angularModules['crmUtil'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmUtil.js'), - ); - // https://github.com/jwstadler/angular-jquery-dialog-service - $angularModules['dialogService'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-jquery-dialog-service/dialog-service.js'), - ); - $angularModules['ngRoute'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-route/angular-route.min.js'), - ); - $angularModules['ngSanitize'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-sanitize/angular-sanitize.min.js'), - ); - $angularModules['ui.utils'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-ui-utils/ui-utils.min.js'), - ); - $angularModules['ui.sortable'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-ui-sortable/sortable.min.js'), - ); - $angularModules['unsavedChanges'] = array( - 'ext' => 'civicrm', - 'js' => array('bower_components/angular-unsavedChanges/dist/unsavedChanges.min.js'), - ); - - $angularModules['statuspage'] = array( - 'ext' => 'civicrm', - 'js' => array('ang/crmStatusPage.js', 'ang/crmStatusPage/*.js'), - 'css' => array('ang/crmStatusPage.css'), - 'partials' => array('ang/crmStatusPage'), - 'settings' => array(), - ); + $angularModules = []; + $angularModules['angularFileUpload'] = include "$civicrm_root/ang/angularFileUpload.ang.php"; + $angularModules['checklist-model'] = include "$civicrm_root/ang/checklist-model.ang.php"; + $angularModules['crmApp'] = include "$civicrm_root/ang/crmApp.ang.php"; + $angularModules['crmAttachment'] = include "$civicrm_root/ang/crmAttachment.ang.php"; + $angularModules['crmAutosave'] = include "$civicrm_root/ang/crmAutosave.ang.php"; + $angularModules['crmCxn'] = include "$civicrm_root/ang/crmCxn.ang.php"; + // $angularModules['crmExample'] = include "$civicrm_root/ang/crmExample.ang.php"; + $angularModules['crmResource'] = include "$civicrm_root/ang/crmResource.ang.php"; + $angularModules['crmRouteBinder'] = include "$civicrm_root/ang/crmRouteBinder.ang.php"; + $angularModules['crmUi'] = include "$civicrm_root/ang/crmUi.ang.php"; + $angularModules['crmUtil'] = include "$civicrm_root/ang/crmUtil.ang.php"; + $angularModules['dialogService'] = include "$civicrm_root/ang/dialogService.ang.php"; + $angularModules['ngRoute'] = include "$civicrm_root/ang/ngRoute.ang.php"; + $angularModules['ngSanitize'] = include "$civicrm_root/ang/ngSanitize.ang.php"; + $angularModules['ui.utils'] = include "$civicrm_root/ang/ui.utils.ang.php"; + $angularModules['ui.bootstrap'] = include "$civicrm_root/ang/ui.bootstrap.ang.php"; + $angularModules['ui.sortable'] = include "$civicrm_root/ang/ui.sortable.ang.php"; + $angularModules['unsavedChanges'] = include "$civicrm_root/ang/unsavedChanges.ang.php"; + $angularModules['statuspage'] = include "$civicrm_root/ang/crmStatusPage.ang.php"; + $angularModules['api4Explorer'] = include "$civicrm_root/ang/api4Explorer.ang.php"; + $angularModules['api4'] = include "$civicrm_root/ang/api4.ang.php"; foreach (\CRM_Core_Component::getEnabledComponents() as $component) { $angularModules = array_merge($angularModules, $component->getAngularModules()); } \CRM_Utils_Hook::angularModules($angularModules); + foreach (array_keys($angularModules) as $module) { + if (!isset($angularModules[$module]['basePages'])) { + $angularModules[$module]['basePages'] = ['civicrm/a']; + } + } $this->modules = $this->resolvePatterns($angularModules); } @@ -171,6 +137,59 @@ public function getModule($name) { return $modules[$name]; } + /** + * Resolve a full list of Angular dependencies. + * + * @param array $names + * List of Angular modules. + * Ex: array('crmMailing'). + * @return array + * List of Angular modules, include all dependencies. + * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute'). + */ + public function resolveDependencies($names) { + $allModules = $this->getModules(); + $visited = []; + $result = $names; + while (($missingModules = array_diff($result, array_keys($visited))) && !empty($missingModules)) { + foreach ($missingModules as $module) { + $visited[$module] = 1; + if (!isset($allModules[$module])) { + \Civi::log()->warning('Unrecognized Angular module {name}. Please ensure that all Angular modules are declared.', [ + 'name' => $module, + 'civi.tag' => 'deprecated', + ]); + } + elseif (isset($allModules[$module]['requires'])) { + $result = array_unique(array_merge($result, $allModules[$module]['requires'])); + } + } + } + sort($result); + return $result; + } + + /** + * Get a list of Angular modules that should be loaded on the given + * base-page. + * + * @param string $basePage + * The name of the base-page for which we want a list of moudles. + * @return array + * List of Angular modules. + * Ex: array('crmMailing', 'crmUi', 'crmUtil', 'ngRoute'). + */ + public function resolveDefaultModules($basePage) { + $modules = $this->getModules(); + $result = []; + foreach ($modules as $moduleName => $module) { + if (in_array($basePage, $module['basePages']) || in_array('*', $module['basePages'])) { + $result[] = $moduleName; + } + } + return $result; + } + /** * Convert any globs in an Angular module to file names. * @@ -180,10 +199,10 @@ public function getModule($name) { * Updated list of Angular modules */ protected function resolvePatterns($modules) { - $newModules = array(); + $newModules = []; foreach ($modules as $moduleKey => $module) { - foreach (array('js', 'css', 'partials') as $fileset) { + foreach (['js', 'css', 'partials'] as $fileset) { if (!isset($module[$fileset])) { continue; } @@ -196,7 +215,7 @@ protected function resolvePatterns($modules) { } /** - * Get the partial HTML documents for a module. + * Get the partial HTML documents for a module (unfiltered). * * @param string $name * Angular module name. @@ -205,9 +224,9 @@ protected function resolvePatterns($modules) { * @throws \Exception * Invalid partials configuration. */ - public function getPartials($name) { + public function getRawPartials($name) { $module = $this->getModule($name); - $result = array(); + $result = []; if (isset($module['partials'])) { foreach ($module['partials'] as $partialDir) { $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir; @@ -217,10 +236,31 @@ public function getPartials($name) { $result[$filename] = file_get_contents($partialDir . '/' . $file); } } + return $result; } return $result; } + /** + * Get the partial HTML documents for a module. + * + * @param string $name + * Angular module name. + * @return array + * Array(string $extFilePath => string $html) + * @throws \Exception + * Invalid partials configuration. + */ + public function getPartials($name) { + $cacheKey = "angular-partials_$name"; + $cacheValue = $this->cache->get($cacheKey); + if ($cacheValue === NULL) { + $cacheValue = ChangeSet::applyResourceFilters($this->getChangeSets(), 'partials', $this->getRawPartials($name)); + $this->cache->set($cacheKey, $cacheValue); + } + return $cacheValue; + } + /** * Get list of translated strings for a module. * @@ -231,14 +271,14 @@ public function getPartials($name) { */ public function getTranslatedStrings($name) { $module = $this->getModule($name); - $result = array(); + $result = []; $strings = $this->getStrings($name); foreach ($strings as $string) { // TODO: should we pass translation domain based on $module[ext] or $module[tsDomain]? // It doesn't look like client side really supports the domain right now... - $translated = ts($string, array( - 'domain' => array($module['ext'], NULL), - )); + $translated = ts($string, [ + 'domain' => [$module['ext'], NULL], + ]); if ($translated != $string) { $result[$string] = $translated; } @@ -256,7 +296,7 @@ public function getTranslatedStrings($name) { */ public function getStrings($name) { $module = $this->getModule($name); - $result = array(); + $result = []; if (isset($module['js'])) { foreach ($module['js'] as $file) { $strings = $this->res->getStrings()->get( @@ -267,19 +307,9 @@ public function getStrings($name) { $result = array_unique(array_merge($result, $strings)); } } - if (isset($module['partials'])) { - foreach ($module['partials'] as $partialDir) { - $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir; - $files = \CRM_Utils_File::findFiles($partialDir, '*.html'); - foreach ($files as $file) { - $strings = $this->res->getStrings()->get( - $module['ext'], - $file, - 'text/html' - ); - $result = array_unique(array_merge($result, $strings)); - } - } + $partials = $this->getPartials($name); + foreach ($partials as $partial) { + $result = array_unique(array_merge($result, \CRM_Utils_JS::parseStrings($partial))); } return $result; } @@ -298,13 +328,18 @@ public function getStrings($name) { * @throws \CRM_Core_Exception */ public function getResources($moduleNames, $resType, $refType) { - $result = array(); + $result = []; $moduleNames = (array) $moduleNames; foreach ($moduleNames as $moduleName) { $module = $this->getModule($moduleName); if (isset($module[$resType])) { foreach ($module[$resType] as $file) { - switch ($refType) { + $refTypeSuffix = ''; + if (is_string($file) && preg_match(';^(assetBuilder|ext)://;', $file)) { + $refTypeSuffix = '-' . parse_url($file, PHP_URL_SCHEME); + } + + switch ($refType . $refTypeSuffix) { case 'path': $result[] = $this->res->getPath($module['ext'], $file); break; @@ -317,7 +352,35 @@ public function getResources($moduleNames, $resType, $refType) { $result[] = $this->res->getUrl($module['ext'], $file, TRUE); break; + case 'path-assetBuilder': + $assetName = parse_url($file, PHP_URL_HOST) . parse_url($file, PHP_URL_PATH); + $assetParams = []; + parse_str('' . parse_url($file, PHP_URL_QUERY), $assetParams); + $result[] = \Civi::service('asset_builder')->getPath($assetName, $assetParams); + break; + + case 'rawUrl-assetBuilder': + case 'cacheUrl-assetBuilder': + $assetName = parse_url($file, PHP_URL_HOST) . parse_url($file, PHP_URL_PATH); + $assetParams = []; + parse_str('' . parse_url($file, PHP_URL_QUERY), $assetParams); + $result[] = \Civi::service('asset_builder')->getUrl($assetName, $assetParams); + break; + + case 'path-ext': + $result[] = $this->res->getPath(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/')); + break; + + case 'rawUrl-ext': + $result[] = $this->res->getUrl(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/')); + break; + + case 'cacheUrl-ext': + $result[] = $this->res->getUrl(parse_url($file, PHP_URL_HOST), ltrim(parse_url($file, PHP_URL_PATH), '/'), TRUE); + break; + case 'settings': + case 'requires': if (!empty($module[$resType])) { $result[$moduleName] = $module[$resType]; } @@ -329,7 +392,29 @@ public function getResources($moduleNames, $resType, $refType) { } } } - return $result; + + return ChangeSet::applyResourceFilters($this->getChangeSets(), $resType, $result); + } + + /** + * @return array + * Array(string $name => ChangeSet $changeSet). + */ + public function getChangeSets() { + if ($this->changeSets === NULL) { + $this->changeSets = []; + \CRM_Utils_Hook::alterAngular($this); + } + return $this->changeSets; + } + + /** + * @param ChangeSet $changeSet + * @return \Civi\Angular\Manager + */ + public function add($changeSet) { + $this->changeSets[$changeSet->getName()] = $changeSet; + return $this; } } diff --git a/Civi/Angular/Page/Main.php b/Civi/Angular/Page/Main.php index 2f945ebae799..c844fd3cc04b 100644 --- a/Civi/Angular/Page/Main.php +++ b/Civi/Angular/Page/Main.php @@ -8,6 +8,7 @@ * @link https://issues.civicrm.org/jira/browse/CRM-14479 */ class Main extends \CRM_Core_Page { + /** * The weight to assign to any Angular JS module files. */ @@ -19,23 +20,25 @@ class Main extends \CRM_Core_Page { * Do not use publicly. Inject your own copy! * * @var \CRM_Core_Resources + * @deprecated */ public $res; - /** * The Angular module manager. * * Do not use publicly. Inject your own copy! * * @var \Civi\Angular\Manager + * @deprecated */ public $angular; /** * The region of the page into which JavaScript will be loaded. * - * @var String + * @var string + * @deprecated */ public $region; @@ -71,57 +74,14 @@ public function run() { * Register resources required by Angular. */ public function registerResources() { - $modules = $this->angular->getModules(); - $page = $this; // PHP 5.3 does not propagate $this to inner functions. - - $this->res->addSettingsFactory(function () use (&$modules, $page) { - // TODO optimization; client-side caching - return array_merge($page->angular->getResources(array_keys($modules), 'settings', 'settings'), array( - 'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), - 'angular' => array( - 'modules' => array_merge(array('ngRoute'), array_keys($modules)), - 'cacheCode' => $page->res->getCacheCode(), - ), - )); - }); - - $this->res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->region, FALSE); - - $headOffset = 0; - $config = \CRM_Core_Config::singleton(); - if ($config->debug) { - foreach ($modules as $moduleName => $module) { - foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { - $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - } - foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { - $this->res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - // addScriptUrl() bypasses the normal string-localization of addScriptFile(), - // but that's OK because all Angular strings (JS+HTML) will load via crmResource. - } - } - } - else { - // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), - // but that's OK because all Angular strings (JS+HTML) will load via crmResource. - $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); - $this->res->addScriptUrl($aggScriptUrl, 120, $this->region); - - // FIXME: The following CSS aggregator doesn't currently handle path-adjustments - which can break icons. - //$aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); - //$this->res->addStyleUrl($aggStyleUrl, 120, $this->region); - - foreach ($this->angular->getResources(array_keys($modules), 'css', 'cacheUrl') as $url) { - $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - } - } + $loader = new \Civi\Angular\AngularLoader(); + $loader->setPageName('civicrm/a'); + $loader->useApp([ + 'activeRoute' => \CRM_Utils_Request::retrieve('route', 'String'), + 'defaultRoute' => NULL, + ]); + $loader->load(); - // If trying to load an Angular page via AJAX, the route must be passed as a - // URL parameter, since the server doesn't receive information about - // URL fragments (i.e, what comes after the #). - \CRM_Core_Resources::singleton()->addSetting(array( - 'angularRoute' => \CRM_Utils_Request::retrieve('route', 'String'), - )); } } diff --git a/Civi/Angular/Page/Modules.php b/Civi/Angular/Page/Modules.php index 2fc3d86368ee..3b6b5a75ba54 100644 --- a/Civi/Angular/Page/Modules.php +++ b/Civi/Angular/Page/Modules.php @@ -20,7 +20,10 @@ class Modules extends \CRM_Core_Page { /** - * See class description. + * Generate asset content (when accessed via older, custom + * "civicrm/ajax/anulgar-modules" route). + * + * @deprecated */ public function run() { /** @@ -59,20 +62,54 @@ public function run() { \CRM_Utils_System::civiExit(); } + /** + * Generate asset content (when accessed via AssetBuilder). + * + * @param \Civi\Core\Event\GenericHookEvent $event + * @see CRM_Utils_hook::buildAsset() + * @see \Civi\Core\AssetBuilder + */ + public static function buildAngularModules($event) { + $page = new Modules(); + $angular = \Civi::service('angular'); + + switch ($event->asset) { + case 'angular-modules.json': + $moduleNames = $page->parseModuleNames(\CRM_Utils_Array::value('modules', $event->params), $angular); + $event->mimeType = 'application/json'; + $event->content = json_encode($page->getMetadata($moduleNames, $angular)); + break; + + case 'angular-modules.js': + $moduleNames = $page->parseModuleNames(\CRM_Utils_Array::value('modules', $event->params), $angular); + $event->mimeType = 'application/javascript'; + $event->content = $page->digestJs($angular->getResources($moduleNames, 'js', 'path')); + break; + + case 'angular-modules.css': + $moduleNames = $page->parseModuleNames(\CRM_Utils_Array::value('modules', $event->params), $angular); + $event->mimeType = 'text/css'; + $event->content = \CRM_Utils_File::concat($angular->getResources($moduleNames, 'css', 'path'), "\n"); + + default: + // Not our problem. + } + } + /** * @param array $files * File paths. * @return string */ public function digestJs($files) { - $scripts = array(); + $scripts = []; foreach ($files as $file) { $scripts[] = file_get_contents($file); } $scripts = \CRM_Utils_JS::dedupeClosures( $scripts, - array('angular', '$', '_'), - array('angular', 'CRM.$', 'CRM._') + ['angular', '$', '_'], + ['angular', 'CRM.$', 'CRM._'] ); // This impl of stripComments currently adds 10-20ms and cuts ~7% return \CRM_Utils_JS::stripComments(implode("\n", $scripts)); @@ -107,10 +144,10 @@ public function parseModuleNames($modulesExpr, $angular) { */ public function getMetadata($moduleNames, $angular) { $modules = $angular->getModules(); - $result = array(); + $result = []; foreach ($moduleNames as $moduleName) { if (isset($modules[$moduleName])) { - $result[$moduleName] = array(); + $result[$moduleName] = []; $result[$moduleName]['domain'] = $modules[$moduleName]['ext']; $result[$moduleName]['js'] = $angular->getResources($moduleName, 'js', 'rawUrl'); $result[$moduleName]['css'] = $angular->getResources($moduleName, 'css', 'rawUrl'); diff --git a/Civi/Api4/ACL.php b/Civi/Api4/ACL.php new file mode 100644 index 000000000000..363823dd20ba --- /dev/null +++ b/Civi/Api4/ACL.php @@ -0,0 +1,54 @@ +streetParsing && !empty($item['street_address'])) { + $item = array_merge($item, \CRM_Core_BAO_Address::parseStreetAddress($item['street_address'])); + } + $item['skip_geocode'] = $this->skipGeocode; + } + return parent::writeObjects($items); + } + +} diff --git a/Civi/Api4/Action/Address/Create.php b/Civi/Api4/Action/Address/Create.php new file mode 100644 index 000000000000..33fffe41083b --- /dev/null +++ b/Civi/Api4/Action/Address/Create.php @@ -0,0 +1,46 @@ +ttl === 0 || $this->ttl === '0') ? 'inf' : $this->ttl; + $result[] = [ + 'id' => $this->contactId, + 'checksum' => \CRM_Contact_BAO_Contact_Utils::generateChecksum($this->contactId, NULL, $ttl), + ]; + } + +} diff --git a/Civi/Api4/Action/Contact/GetFields.php b/Civi/Api4/Action/Contact/GetFields.php new file mode 100644 index 000000000000..4db97e5012aa --- /dev/null +++ b/Civi/Api4/Action/Contact/GetFields.php @@ -0,0 +1,54 @@ +checkPermissions && !\CRM_Core_Permission::check([$apiKeyPerms])) { + unset($fields['api_key']); + } + + return $fields; + } + +} diff --git a/Civi/Api4/Action/Contact/ValidateChecksum.php b/Civi/Api4/Action/Contact/ValidateChecksum.php new file mode 100644 index 000000000000..f8038f854e73 --- /dev/null +++ b/Civi/Api4/Action/Contact/ValidateChecksum.php @@ -0,0 +1,77 @@ + \CRM_Contact_BAO_Contact_Utils::validChecksum($this->contactId, $this->checksum), + ]; + } + +} diff --git a/Civi/Api4/Action/CustomValue/Create.php b/Civi/Api4/Action/CustomValue/Create.php new file mode 100644 index 000000000000..7f174b7e4e00 --- /dev/null +++ b/Civi/Api4/Action/CustomValue/Create.php @@ -0,0 +1,46 @@ +_itemsToGet('name'); + /** @var \Civi\Api4\Service\Spec\SpecGatherer $gatherer */ + $gatherer = \Civi::container()->get('spec_gatherer'); + $spec = $gatherer->getSpec('Custom_' . $this->getCustomGroup(), $this->getAction(), $this->includeCustom); + return SpecFormatter::specToArray($spec->getFields($fields), $this->loadOptions); + } + + /** + * @inheritDoc + */ + public function getParamInfo($param = NULL) { + $info = parent::getParamInfo($param); + if (!$param) { + // This param is meaningless here. + unset($info['includeCustom']); + } + return $info; + } + +} diff --git a/Civi/Api4/Action/CustomValue/Replace.php b/Civi/Api4/Action/CustomValue/Replace.php new file mode 100644 index 000000000000..347be6500592 --- /dev/null +++ b/Civi/Api4/Action/CustomValue/Replace.php @@ -0,0 +1,46 @@ +currentDomain) { + $this->addWhere('id', '=', \CRM_Core_Config::domainID()); + } + return parent::getObjects(); + } + +} diff --git a/Civi/Api4/Action/Entity/Get.php b/Civi/Api4/Action/Entity/Get.php new file mode 100644 index 000000000000..a0ca45001d88 --- /dev/null +++ b/Civi/Api4/Action/Entity/Get.php @@ -0,0 +1,140 @@ +getPath('[civicrm.root]/Civi.php')], + array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath') + ); + foreach ($locations as $location) { + $dir = \CRM_Utils_File::addTrailingSlash(dirname($location)) . 'Civi/Api4'; + if (is_dir($dir)) { + foreach (glob("$dir/*.php") as $file) { + $matches = []; + preg_match('/(\w*).php/', $file, $matches); + $entity = ['name' => $matches[1]]; + if ($this->_isFieldSelected('description') || $this->_isFieldSelected('comment')) { + $this->addDocs($entity); + } + $entities[$matches[1]] = $entity; + } + } + } + unset($entities['CustomValue']); + + if ($this->includeCustom) { + $this->addCustomEntities($entities); + } + + ksort($entities); + return $entities; + } + + /** + * Add custom-field pseudo-entities + * + * @param $entities + * @throws \API_Exception + */ + private function addCustomEntities(&$entities) { + $customEntities = CustomGroup::get() + ->addWhere('is_multiple', '=', 1) + ->addWhere('is_active', '=', 1) + ->setSelect(['name', 'title', 'help_pre', 'help_post', 'extends']) + ->setCheckPermissions(FALSE) + ->execute(); + foreach ($customEntities as $customEntity) { + $fieldName = 'Custom_' . $customEntity['name']; + $entities[$fieldName] = [ + 'name' => $fieldName, + 'description' => $customEntity['title'] . ' custom group - extends ' . $customEntity['extends'], + ]; + if (!empty($customEntity['help_pre'])) { + $entities[$fieldName]['comment'] = $this->plainTextify($customEntity['help_pre']); + } + if (!empty($customEntity['help_post'])) { + $pre = empty($entities[$fieldName]['comment']) ? '' : $entities[$fieldName]['comment'] . "\n\n"; + $entities[$fieldName]['comment'] = $pre . $this->plainTextify($customEntity['help_post']); + } + } + } + + /** + * Convert html to plain text. + * + * @param $input + * @return mixed + */ + private function plainTextify($input) { + return html_entity_decode(strip_tags($input), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + /** + * Add info from code docblock. + * + * @param $entity + */ + private function addDocs(&$entity) { + $reflection = new \ReflectionClass("\\Civi\\Api4\\" . $entity['name']); + $entity += ReflectionUtils::getCodeDocs($reflection); + unset($entity['package'], $entity['method']); + } + +} diff --git a/Civi/Api4/Action/Entity/GetLinks.php b/Civi/Api4/Action/Entity/GetLinks.php new file mode 100644 index 000000000000..c57e071a8c93 --- /dev/null +++ b/Civi/Api4/Action/Entity/GetLinks.php @@ -0,0 +1,86 @@ +get('schema_map'); + foreach ($schema->getTables() as $table) { + $entity = CoreUtil::getApiNameFromTableName($table->getName()); + // Since this is an api function, exclude tables that don't have an api + if (class_exists('\Civi\Api4\\' . $entity)) { + $item = [ + 'entity' => $entity, + 'table' => $table->getName(), + 'links' => [], + ]; + foreach ($table->getTableLinks() as $link) { + $link = $link->toArray(); + $link['entity'] = CoreUtil::getApiNameFromTableName($link['targetTable']); + $item['links'][] = $link; + } + $result[] = $item; + } + } + return $result; + } + + public function fields() { + return [ + [ + 'name' => 'entity', + ], + [ + 'name' => 'table', + ], + [ + 'name' => 'links', + 'data_type' => 'Array', + ], + ]; + } + +} diff --git a/Civi/Api4/Action/Event/Get.php b/Civi/Api4/Action/Event/Get.php new file mode 100644 index 000000000000..0acc27e2e30b --- /dev/null +++ b/Civi/Api4/Action/Event/Get.php @@ -0,0 +1,47 @@ +_actionsToGet = $this->_itemsToGet('name'); + + $entityReflection = new \ReflectionClass('\Civi\Api4\\' . $this->_entityName); + foreach ($entityReflection->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC) as $method) { + $actionName = $method->getName(); + if ($actionName != 'permissions' && $actionName[0] != '_') { + $this->loadAction($actionName); + } + } + if (!$this->_actionsToGet || count($this->_actionsToGet) > count($this->_actions)) { + // Search for entity-specific actions in extensions + foreach (\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles() as $ext) { + $dir = \CRM_Utils_File::addTrailingSlash(dirname($ext['filePath'])); + $this->scanDir($dir . 'Civi/Api4/Action/' . $this->_entityName); + } + // Search for entity-specific actions in core + $this->scanDir(\CRM_Utils_File::addTrailingSlash(__DIR__) . $this->_entityName); + } + ksort($this->_actions); + return $this->_actions; + } + + /** + * @param $dir + */ + private function scanDir($dir) { + if (is_dir($dir)) { + foreach (glob("$dir/*.php") as $file) { + $matches = []; + preg_match('/(\w*).php/', $file, $matches); + $actionName = array_pop($matches); + $actionClass = new \ReflectionClass('\\Civi\\Api4\\Action\\' . $this->_entityName . '\\' . $actionName); + if ($actionClass->isInstantiable() && $actionClass->isSubclassOf('\\Civi\\Api4\\Generic\\AbstractAction')) { + $this->loadAction(lcfirst($actionName)); + } + } + } + } + + /** + * @param $actionName + */ + private function loadAction($actionName) { + try { + if (!isset($this->_actions[$actionName]) && (!$this->_actionsToGet || in_array($actionName, $this->_actionsToGet))) { + $action = ActionUtil::getAction($this->getEntityName(), $actionName); + if (is_object($action)) { + $this->_actions[$actionName] = ['name' => $actionName]; + if ($this->_isFieldSelected('description') || $this->_isFieldSelected('comment')) { + $actionReflection = new \ReflectionClass($action); + $actionInfo = ReflectionUtils::getCodeDocs($actionReflection); + unset($actionInfo['method']); + $this->_actions[$actionName] += $actionInfo; + } + if ($this->_isFieldSelected('params')) { + $this->_actions[$actionName]['params'] = $action->getParamInfo(); + // Language param is only relevant on multilingual sites + $languageLimit = (array) \Civi::settings()->get('languageLimit'); + if (count($languageLimit) < 2) { + unset($this->_actions[$actionName]['params']['language']); + } + elseif (isset($this->_actions[$actionName]['params']['language'])) { + $this->_actions[$actionName]['params']['language']['options'] = array_keys($languageLimit); + } + } + } + } + } + catch (NotImplementedException $e) { + } + } + + public function fields() { + return [ + [ + 'name' => 'name', + 'data_type' => 'String', + ], + [ + 'name' => 'description', + 'data_type' => 'String', + ], + [ + 'name' => 'comment', + 'data_type' => 'String', + ], + [ + 'name' => 'params', + 'data_type' => 'Array', + ], + ]; + } + +} diff --git a/Civi/Api4/Action/GroupContact/Create.php b/Civi/Api4/Action/GroupContact/Create.php new file mode 100644 index 000000000000..dda98c5490c7 --- /dev/null +++ b/Civi/Api4/Action/GroupContact/Create.php @@ -0,0 +1,46 @@ +method; + $item['tracking'] = $this->tracking; + } + return parent::writeObjects($items); + } + +} diff --git a/Civi/Api4/Action/GroupContact/Save.php b/Civi/Api4/Action/GroupContact/Save.php new file mode 100644 index 000000000000..1ac443e9efb6 --- /dev/null +++ b/Civi/Api4/Action/GroupContact/Save.php @@ -0,0 +1,46 @@ +findDomains(); + $meta = []; + foreach ($this->domainId as $domain) { + $meta[$domain] = $this->validateSettings($domain); + } + foreach ($this->domainId as $domain) { + $settingsBag = $this->contactId ? \Civi::contactSettings($this->contactId, $domain) : \Civi::settings($domain); + $this->processSettings($result, $settingsBag, $meta[$domain], $domain); + } + } + + /** + * Checks that really ought to be taken care of by Civi::settings + * + * @param int $domain + * @return array + * @throws \API_Exception + */ + protected function validateSettings($domain) { + $meta = \Civi\Core\SettingsMetadata::getMetadata([], $domain); + $names = isset($this->values) ? array_keys($this->values) : $this->select; + $invalid = array_diff($names, array_keys($meta)); + if ($invalid) { + throw new \API_Exception("Unknown settings for domain $domain: " . implode(', ', $invalid)); + } + if (isset($this->values)) { + foreach ($this->values as $name => &$value) { + \CRM_Core_BAO_Setting::validateSetting($value, $meta[$name]); + } + } + return $meta; + } + + protected function findDomains() { + if ($this->domainId == 'all') { + $this->domainId = Domain::get()->setCheckPermissions(FALSE)->addSelect('id')->execute()->column('id'); + } + elseif ($this->domainId) { + $this->domainId = (array) $this->domainId; + $domains = Domain::get()->setCheckPermissions(FALSE)->addSelect('id')->execute()->column('id'); + $invalid = array_diff($this->domainId, $domains); + if ($invalid) { + throw new \API_Exception('Invalid domain id: ' . implode(', ', $invalid)); + } + } + else { + $this->domainId = [\CRM_Core_Config::domainID()]; + } + } + +} diff --git a/Civi/Api4/Action/Setting/Get.php b/Civi/Api4/Action/Setting/Get.php new file mode 100644 index 000000000000..a91408177b29 --- /dev/null +++ b/Civi/Api4/Action/Setting/Get.php @@ -0,0 +1,90 @@ +select) { + foreach ($this->select as $name) { + $result[] = [ + 'name' => $name, + 'value' => $settingsBag->get($name), + 'domain_id' => $domain, + ]; + } + } + else { + foreach ($settingsBag->all() as $name => $value) { + $result[] = [ + 'name' => $name, + 'value' => $value, + 'domain_id' => $domain, + ]; + } + } + foreach ($result as $name => &$setting) { + if (isset($setting['value']) && !empty($meta[$name]['serialize'])) { + $setting['value'] = \CRM_Core_DAO::unSerializeField($setting['value'], $meta[$name]['serialize']); + } + } + } + +} diff --git a/Civi/Api4/Action/Setting/GetFields.php b/Civi/Api4/Action/Setting/GetFields.php new file mode 100644 index 000000000000..7cd7cea67643 --- /dev/null +++ b/Civi/Api4/Action/Setting/GetFields.php @@ -0,0 +1,119 @@ +_itemsToGet('name'); + // $filter = $names ? ['name' => $names] : []; + $filter = []; + return \Civi\Core\SettingsMetadata::getMetadata($filter, $this->domainId, $this->loadOptions); + } + + public function fields() { + return [ + [ + 'name' => 'name', + 'data_type' => 'String', + ], + [ + 'name' => 'title', + 'data_type' => 'String', + ], + [ + 'name' => 'description', + 'data_type' => 'String', + ], + [ + 'name' => 'help_text', + 'data_type' => 'String', + ], + [ + 'name' => 'default', + 'data_type' => 'String', + ], + [ + 'name' => 'pseudoconstant', + 'data_type' => 'String', + ], + [ + 'name' => 'options', + 'data_type' => 'Array', + ], + [ + 'name' => 'group_name', + 'data_type' => 'String', + ], + [ + 'name' => 'group', + 'data_type' => 'String', + ], + [ + 'name' => 'html_type', + 'data_type' => 'String', + ], + [ + 'name' => 'add', + 'data_type' => 'String', + ], + [ + 'name' => 'serialize', + 'data_type' => 'Integer', + ], + [ + 'name' => 'data_type', + 'data_type' => 'Integer', + ], + ]; + } + +} diff --git a/Civi/Api4/Action/Setting/Revert.php b/Civi/Api4/Action/Setting/Revert.php new file mode 100644 index 000000000000..d8a340662db8 --- /dev/null +++ b/Civi/Api4/Action/Setting/Revert.php @@ -0,0 +1,81 @@ +select as $name) { + $settingsBag->revert($name); + $result[] = [ + 'name' => $name, + 'value' => $settingsBag->get($name), + 'domain_id' => $domain, + ]; + } + foreach ($result as $name => &$setting) { + if (isset($setting['value']) && !empty($meta[$name]['serialize'])) { + $setting['value'] = \CRM_Core_DAO::unSerializeField($setting['value'], $meta[$name]['serialize']); + } + } + } + +} diff --git a/Civi/Api4/Action/Setting/Set.php b/Civi/Api4/Action/Setting/Set.php new file mode 100644 index 000000000000..d0ea403ecdc2 --- /dev/null +++ b/Civi/Api4/Action/Setting/Set.php @@ -0,0 +1,79 @@ +values as $name => $value) { + if (isset($value) && !empty($meta[$name]['serialize'])) { + $value = \CRM_Core_DAO::serializeField($value, $meta[$name]['serialize']); + } + $settingsBag->set($name, $value); + $result[] = [ + 'name' => $name, + 'value' => $this->values[$name], + 'domain_id' => $domain, + ]; + } + } + +} diff --git a/Civi/Api4/Action/System/Check.php b/Civi/Api4/Action/System/Check.php new file mode 100644 index 000000000000..e07ac0d1de6b --- /dev/null +++ b/Civi/Api4/Action/System/Check.php @@ -0,0 +1,118 @@ +toArray(); + } + return $messages; + } + + public static function fields() { + return [ + [ + 'name' => 'name', + 'title' => 'Name', + 'description' => 'Unique identifier', + 'data_type' => 'String', + ], + [ + 'name' => 'title', + 'title' => 'Title', + 'description' => 'Short title text', + 'data_type' => 'String', + ], + [ + 'name' => 'message', + 'title' => 'Message', + 'description' => 'Long description html', + 'data_type' => 'String', + ], + [ + 'name' => 'help', + 'title' => 'Help', + 'description' => 'Optional extra help (html string)', + 'data_type' => 'String', + ], + [ + 'name' => 'icon', + 'description' => 'crm-i class of icon to display with message', + 'data_type' => 'String', + ], + [ + 'name' => 'severity', + 'title' => 'Severity', + 'description' => 'Psr\Log\LogLevel string', + 'data_type' => 'String', + 'options' => array_combine(\CRM_Utils_Check::getSeverityList(), \CRM_Utils_Check::getSeverityList()), + ], + [ + 'name' => 'severity_id', + 'title' => 'Severity ID', + 'description' => 'Integer representation of Psr\Log\LogLevel', + 'data_type' => 'Integer', + 'options' => \CRM_Utils_Check::getSeverityList(), + ], + [ + 'name' => 'is_visible', + 'title' => 'is visible', + 'description' => '0 if message has been hidden by the user', + 'data_type' => 'Boolean', + ], + [ + 'name' => 'hidden_until', + 'title' => 'Hidden until', + 'description' => 'When will hidden message be visible again?', + 'data_type' => 'Date', + ], + [ + 'name' => 'actions', + 'title' => 'Actions', + 'description' => 'List of actions user can perform', + 'data_type' => 'Array', + ], + ]; + } + +} diff --git a/Civi/Api4/Action/System/Flush.php b/Civi/Api4/Action/System/Flush.php new file mode 100644 index 000000000000..71140ee35b77 --- /dev/null +++ b/Civi/Api4/Action/System/Flush.php @@ -0,0 +1,67 @@ +triggers, $this->session); + } + +} diff --git a/Civi/Api4/ActionSchedule.php b/Civi/Api4/ActionSchedule.php new file mode 100644 index 000000000000..f3274a451804 --- /dev/null +++ b/Civi/Api4/ActionSchedule.php @@ -0,0 +1,53 @@ + 'name'], + ['name' => 'description'], + ['name' => 'comment'], + ]; + }); + } + + /** + * @return Action\Entity\GetLinks + */ + public static function getLinks() { + return new Action\Entity\GetLinks('Entity', __FUNCTION__); + } + + /** + * @return array + */ + public static function permissions() { + return [ + 'default' => ['access CiviCRM'], + ]; + } + +} diff --git a/Civi/Api4/EntityTag.php b/Civi/Api4/EntityTag.php new file mode 100644 index 000000000000..b58b09332138 --- /dev/null +++ b/Civi/Api4/EntityTag.php @@ -0,0 +1,47 @@ +request = $request; + } + + /** + * @return \Civi\Api4\Generic\AbstractAction + */ + public function getRequest() { + return $this->request; + } + + /** + * @param $request + */ + public function setRequest(AbstractAction $request) { + $this->request = $request; + } + +} diff --git a/Civi/Api4/Event/PostSelectQueryEvent.php b/Civi/Api4/Event/PostSelectQueryEvent.php new file mode 100644 index 000000000000..30036cced398 --- /dev/null +++ b/Civi/Api4/Event/PostSelectQueryEvent.php @@ -0,0 +1,99 @@ +results = $results; + $this->query = $query; + } + + /** + * @return array + */ + public function getResults() { + return $this->results; + } + + /** + * @param array $results + * @return $this + */ + public function setResults($results) { + $this->results = $results; + + return $this; + } + + /** + * @return \Civi\Api4\Query\Api4SelectQuery + */ + public function getQuery() { + return $this->query; + } + + /** + * @param \Civi\Api4\Query\Api4SelectQuery $query + * @return $this + */ + public function setQuery($query) { + $this->query = $query; + + return $this; + } + +} diff --git a/Civi/Api4/Event/SchemaMapBuildEvent.php b/Civi/Api4/Event/SchemaMapBuildEvent.php new file mode 100644 index 000000000000..2a63022b18ee --- /dev/null +++ b/Civi/Api4/Event/SchemaMapBuildEvent.php @@ -0,0 +1,74 @@ +schemaMap = $schemaMap; + } + + /** + * @return \Civi\Api4\Service\Schema\SchemaMap + */ + public function getSchemaMap() { + return $this->schemaMap; + } + + /** + * @param \Civi\Api4\Service\Schema\SchemaMap $schemaMap + * + * @return $this + */ + public function setSchemaMap($schemaMap) { + $this->schemaMap = $schemaMap; + + return $this; + } + +} diff --git a/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php b/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php new file mode 100644 index 000000000000..d536413b1b94 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ActivityPreCreationSubscriber.php @@ -0,0 +1,76 @@ +getValue('activity_type'); + if ($activityType) { + $result = OptionValue::get() + ->setCheckPermissions(FALSE) + ->addWhere('name', '=', $activityType) + ->addWhere('option_group.name', '=', 'activity_type') + ->execute(); + + if ($result->count() !== 1) { + throw new \Exception('Activity type must match a *single* type'); + } + + $request->addValue('activity_type_id', $result->first()['value']); + } + } + + /** + * @param \Civi\Api4\Generic\DAOCreateAction $request + * + * @return bool + */ + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'Activity'; + } + +} diff --git a/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php b/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php new file mode 100644 index 000000000000..9dc968802321 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ActivitySchemaMapSubscriber.php @@ -0,0 +1,75 @@ + 'onSchemaBuild', + ]; + } + + /** + * @param \Civi\Api4\Event\SchemaMapBuildEvent $event + */ + public function onSchemaBuild(SchemaMapBuildEvent $event) { + $schema = $event->getSchemaMap(); + $table = $schema->getTableByName('civicrm_activity'); + + $middleAlias = \CRM_Utils_String::createRandom(10, implode(range('a', 'z'))); + $middleLink = new ActivityToActivityContactAssigneesJoinable($middleAlias); + + $bridge = new BridgeJoinable('civicrm_contact', 'id', 'assignees', $middleLink); + $bridge->setBaseTable('civicrm_activity_contact'); + $bridge->setJoinType(Joinable::JOIN_TYPE_ONE_TO_MANY); + + $table->addTableLink('contact_id', $bridge); + } + +} diff --git a/Civi/Api4/Event/Subscriber/ContactPreSaveSubscriber.php b/Civi/Api4/Event/Subscriber/ContactPreSaveSubscriber.php new file mode 100644 index 000000000000..d8bc34d584b3 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ContactPreSaveSubscriber.php @@ -0,0 +1,60 @@ +getEntityName() === 'Contact'; + } + +} diff --git a/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php b/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php new file mode 100644 index 000000000000..bcde7fb20c78 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ContactSchemaMapSubscriber.php @@ -0,0 +1,89 @@ + 'onSchemaBuild', + ]; + } + + /** + * @param \Civi\Api4\Event\SchemaMapBuildEvent $event + */ + public function onSchemaBuild(SchemaMapBuildEvent $event) { + $schema = $event->getSchemaMap(); + $table = $schema->getTableByName('civicrm_contact'); + $this->addCreatedActivitiesLink($table); + $this->fixPreferredLanguageAlias($table); + } + + /** + * @param \Civi\Api4\Service\Schema\Table $table + */ + private function addCreatedActivitiesLink($table) { + $alias = 'created_activities'; + $joinable = new Joinable('civicrm_activity_contact', 'contact_id', $alias); + $joinable->addCondition($alias . '.record_type_id = 1'); + $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY); + $table->addTableLink('id', $joinable); + } + + /** + * @param \Civi\Api4\Service\Schema\Table $table + */ + private function fixPreferredLanguageAlias($table) { + foreach ($table->getExternalLinks() as $link) { + if ($link->getAlias() === 'languages') { + $link->setAlias('preferred_language'); + return; + } + } + } + +} diff --git a/Civi/Api4/Event/Subscriber/ContributionPreSaveSubscriber.php b/Civi/Api4/Event/Subscriber/ContributionPreSaveSubscriber.php new file mode 100644 index 000000000000..73ffd4659686 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ContributionPreSaveSubscriber.php @@ -0,0 +1,53 @@ +getEntityName() === 'Contribution'; + } + +} diff --git a/Civi/Api4/Event/Subscriber/CustomFieldPreSaveSubscriber.php b/Civi/Api4/Event/Subscriber/CustomFieldPreSaveSubscriber.php new file mode 100644 index 000000000000..d5e4cafb220b --- /dev/null +++ b/Civi/Api4/Event/Subscriber/CustomFieldPreSaveSubscriber.php @@ -0,0 +1,73 @@ + $value) { + // Translate simple key/value pairs into full-blown option values + if (!is_array($value)) { + $value = [ + 'label' => $value, + 'value' => $key, + 'is_active' => 1, + 'weight' => $weight, + ]; + $key = $weight++; + } + $field['option_label'][$key] = $value['label']; + $field['option_value'][$key] = $value['value']; + $field['option_status'][$key] = $value['is_active']; + $field['option_weight'][$key] = $value['weight']; + } + } + $field['option_type'] = !empty($field['option_values']); + } + + public function applies(AbstractAction $request) { + return $request->getEntityName() === 'CustomField'; + } + +} diff --git a/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php b/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php new file mode 100644 index 000000000000..2c0aec0c2b4c --- /dev/null +++ b/Civi/Api4/Event/Subscriber/CustomGroupPreCreationSubscriber.php @@ -0,0 +1,65 @@ +getValue('extends'); + $title = $request->getValue('title'); + $name = $request->getValue('name'); + + if (is_string($extends)) { + $request->addValue('extends', [$extends]); + } + + if (NULL === $title && $name) { + $request->addValue('title', $name); + } + } + + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'CustomGroup'; + } + +} diff --git a/Civi/Api4/Event/Subscriber/Generic/AbstractPrepareSubscriber.php b/Civi/Api4/Event/Subscriber/Generic/AbstractPrepareSubscriber.php new file mode 100644 index 000000000000..ce96848e1154 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/Generic/AbstractPrepareSubscriber.php @@ -0,0 +1,60 @@ + 'onApiPrepare', + ]; + } + + /** + * @param \Civi\API\Event\PrepareEvent $event + */ + abstract public function onApiPrepare(PrepareEvent $event); + +} diff --git a/Civi/Api4/Event/Subscriber/Generic/PreCreationSubscriber.php b/Civi/Api4/Event/Subscriber/Generic/PreCreationSubscriber.php new file mode 100644 index 000000000000..6ebd9fb63284 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/Generic/PreCreationSubscriber.php @@ -0,0 +1,86 @@ +getApiRequest(); + if (!$apiRequest instanceof DAOCreateAction) { + return; + } + + $this->addDefaultCreationValues($apiRequest); + if ($this->applies($apiRequest)) { + $this->modify($apiRequest); + } + } + + /** + * Modify the request + * + * @param \Civi\Api4\Generic\DAOCreateAction $request + * + * @return void + */ + abstract protected function modify(DAOCreateAction $request); + + /** + * Check if this subscriber should be applied to the request + * + * @param \Civi\Api4\Generic\DAOCreateAction $request + * + * @return bool + */ + abstract protected function applies(DAOCreateAction $request); + + /** + * Sets default values common to all creation requests + * + * @param \Civi\Api4\Generic\DAOCreateAction $request + */ + protected function addDefaultCreationValues(DAOCreateAction $request) { + } + +} diff --git a/Civi/Api4/Event/Subscriber/Generic/PreSaveSubscriber.php b/Civi/Api4/Event/Subscriber/Generic/PreSaveSubscriber.php new file mode 100644 index 000000000000..7c80adf76f83 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/Generic/PreSaveSubscriber.php @@ -0,0 +1,89 @@ +getApiRequest(); + + if ($apiRequest instanceof AbstractAction && $this->applies($apiRequest)) { + if ( + ($apiRequest instanceof AbstractCreateAction && $this->supportedOperation !== 'update') || + ($apiRequest instanceof AbstractUpdateAction && $this->supportedOperation !== 'create') + ) { + $values = $apiRequest->getValues(); + $this->modify($values, $apiRequest); + $apiRequest->setValues($values); + } + } + } + + /** + * Modify the item about to be saved + * + * @param array $item + * @param \Civi\Api4\Generic\AbstractAction $request + * + */ + abstract protected function modify(&$item, AbstractAction $request); + + /** + * Check if this subscriber should be applied to the request + * + * @param \Civi\Api4\Generic\AbstractAction $request + * + * @return bool + */ + abstract protected function applies(AbstractAction $request); + +} diff --git a/Civi/Api4/Event/Subscriber/IsCurrentSubscriber.php b/Civi/Api4/Event/Subscriber/IsCurrentSubscriber.php new file mode 100644 index 000000000000..4f9610ec6b97 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/IsCurrentSubscriber.php @@ -0,0 +1,74 @@ +getApiRequest(); + if ($action['version'] == 4 && method_exists($action, 'getCurrent') + && in_array('Civi\Api4\Generic\Traits\IsCurrentTrait', ReflectionUtils::getTraits($action)) + ) { + $fields = $action->entityFields(); + if ($action->getCurrent()) { + if (isset($fields['is_active'])) { + $action->addWhere('is_active', '=', '1'); + } + $action->addClause('OR', ['start_date', 'IS NULL'], ['start_date', '<=', 'now']); + $action->addClause('OR', ['end_date', 'IS NULL'], ['end_date', '>=', 'now']); + } + elseif ($action->getCurrent() === FALSE) { + $conditions = [['end_date', '<', 'now'], ['start_date', '>', 'now']]; + if (isset($fields['is_active'])) { + $conditions[] = ['is_active', '=', '0']; + } + $action->addClause('OR', $conditions); + } + } + } + +} diff --git a/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php b/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php new file mode 100644 index 000000000000..5795a8f893ea --- /dev/null +++ b/Civi/Api4/Event/Subscriber/OptionValuePreCreationSubscriber.php @@ -0,0 +1,85 @@ +setOptionGroupId($request); + } + + /** + * @param \Civi\Api4\Generic\DAOCreateAction $request + * + * @return bool + */ + protected function applies(DAOCreateAction $request) { + return $request->getEntityName() === 'OptionValue'; + } + + /** + * @param \Civi\Api4\Generic\DAOCreateAction $request + * @throws \API_Exception + * @throws \Exception + */ + private function setOptionGroupId(DAOCreateAction $request) { + $optionGroupName = $request->getValue('option_group'); + if (!$optionGroupName || $request->getValue('option_group_id')) { + return; + } + + $optionGroup = OptionGroup::get() + ->setCheckPermissions(FALSE) + ->addSelect('id') + ->addWhere('name', '=', $optionGroupName) + ->execute(); + + if ($optionGroup->count() !== 1) { + throw new \Exception('Option group name must match only a single group'); + } + + $request->addValue('option_group_id', $optionGroup->first()['id']); + } + +} diff --git a/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php b/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php new file mode 100644 index 000000000000..b63fdc553d7a --- /dev/null +++ b/Civi/Api4/Event/Subscriber/PermissionCheckSubscriber.php @@ -0,0 +1,66 @@ + [ + ['onApiAuthorize', Events::W_LATE], + ], + ]; + } + + /** + * @param \Civi\API\Event\AuthorizeEvent $event + * API authorization event. + */ + public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { + /* @var \Civi\Api4\Generic\AbstractAction $apiRequest */ + $apiRequest = $event->getApiRequest(); + if ($apiRequest['version'] == 4) { + if (!$apiRequest->getCheckPermissions() || $apiRequest->isAuthorized()) { + $event->authorize(); + $event->stopPropagation(); + } + } + } + +} diff --git a/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php new file mode 100644 index 000000000000..22da84d960f9 --- /dev/null +++ b/Civi/Api4/Event/Subscriber/PostSelectQuerySubscriber.php @@ -0,0 +1,366 @@ + 'onPostQuery', + ]; + } + + /** + * @param \Civi\Api4\Event\PostSelectQueryEvent $event + */ + public function onPostQuery(PostSelectQueryEvent $event) { + $results = $event->getResults(); + $event->setResults($this->postRun($results, $event->getQuery())); + } + + /** + * @param array $results + * @param \Civi\Api4\Query\Api4SelectQuery $query + * + * @return array + */ + protected function postRun(array $results, Api4SelectQuery $query) { + if (empty($results)) { + return $results; + } + + $fieldSpec = $query->getApiFieldSpec(); + $this->unserializeFields($results, $query->getEntity(), $fieldSpec); + + // Group the selects to avoid queries for each field + $groupedSelects = $this->getNtoManyJoinSelects($query); + foreach ($groupedSelects as $finalAlias => $selects) { + $joinPath = $query->getPathJoinTypes($selects[0]); + $selects = $this->formatSelects($finalAlias, $selects, $query); + $joinResults = $this->getJoinResults($query, $finalAlias, $selects); + $this->formatJoinResults($joinResults, $query, $finalAlias); + + // Insert join results into original result + foreach ($results as &$primaryResult) { + $baseId = $primaryResult['id']; + $filtered = array_filter($joinResults, function ($res) use ($baseId) { + return ($res['_base_id'] === $baseId); + }); + $filtered = array_values($filtered); + ArrayInsertionUtil::insert($primaryResult, $joinPath, $filtered); + } + } + + return array_values($results); + } + + /** + * @param array $joinResults + * @param \Civi\Api4\Query\Api4SelectQuery $query + * @param string $alias + */ + private function formatJoinResults(&$joinResults, $query, $alias) { + $join = $query->getJoinedTable($alias); + $fields = []; + foreach ($join->getEntityFields() as $field) { + $name = explode('.', $field->getName()); + $fields[array_pop($name)] = $field->toArray(); + } + if ($fields) { + $this->unserializeFields($joinResults, NULL, $fields); + } + } + + /** + * Unserialize values + * + * @param array $results + * @param string $entity + * @param array $fields + */ + protected function unserializeFields(&$results, $entity, $fields = []) { + foreach ($results as &$result) { + foreach ($result as $field => &$value) { + if (!empty($fields[$field]['serialize']) && is_string($value)) { + $serializationType = $fields[$field]['serialize']; + $value = \CRM_Core_DAO::unSerializeField($value, $serializationType); + } + } + } + } + + /** + * Find only those joins that need to be handled by a separate query and weren't done in the main query. + * + * @param \Civi\Api4\Query\Api4SelectQuery $query + * + * @return array + */ + private function getNtoManyJoinSelects(Api4SelectQuery $query) { + $fkAliases = $query->getFkSelectAliases(); + $joinedDotSelects = array_filter( + $query->getSelect(), + function ($select) use ($fkAliases, $query) { + return isset($fkAliases[$select]) && array_filter($query->getPathJoinTypes($select)); + } + ); + + $selects = []; + // group related selects by alias so they can be executed in one query + foreach ($joinedDotSelects as $select) { + $parts = explode('.', $select); + $finalAlias = $parts[count($parts) - 2]; + $selects[$finalAlias][] = $select; + } + + // sort by depth, e.g. email selects should be done before email.location + uasort($selects, function ($a, $b) { + $aFirst = $a[0]; + $bFirst = $b[0]; + return substr_count($aFirst, '.') > substr_count($bFirst, '.'); + }); + + return $selects; + } + + /** + * @param array $selects + * @param $serializationType + * @param \Civi\Api4\Query\Api4SelectQuery $query + * + * @return array + */ + private function getResultsForSerializedField( + array $selects, + $serializationType, + Api4SelectQuery $query + ) { + // Get the alias (Selects are grouped and all target the same table) + $sampleField = current($selects); + $alias = strstr($sampleField, '.', TRUE); + + // Fetch the results with the serialized field + $selects['serialized'] = $query::MAIN_TABLE_ALIAS . '.' . $alias; + $serializedResults = $this->runWithNewSelects($selects, $query); + $newResults = []; + + // Create a new results array, with a separate entry for each option value + foreach ($serializedResults as $result) { + $optionValues = \CRM_Core_DAO::unSerializeField( + $result['serialized'], + $serializationType + ); + unset($result['serialized']); + foreach ($optionValues as $value) { + $newResults[] = array_merge($result, ['value' => $value]); + } + } + + $optionValueValues = array_unique(array_column($newResults, 'value')); + $optionValues = $this->getOptionValuesFromValues( + $selects, + $query, + $optionValueValues + ); + $valueField = $alias . '.value'; + + // Index by value + foreach ($optionValues as $key => $subResult) { + $optionValues[$subResult['value']] = $subResult; + unset($subResult[$key]); + + // Exclude 'value' if not in original selects + if (!in_array($valueField, $selects)) { + unset($optionValues[$subResult['value']]['value']); + } + } + + // Replace serialized with the sub-select results + foreach ($newResults as &$result) { + $result = array_merge($result, $optionValues[$result['value']]); + unset($result['value']); + } + + return $newResults; + } + + /** + * Prepares selects for the subquery to fetch join results + * + * @param string $alias + * @param array $selects + * @param \Civi\Api4\Query\Api4SelectQuery $query + * + * @return array + */ + private function formatSelects($alias, $selects, Api4SelectQuery $query) { + $mainAlias = $query::MAIN_TABLE_ALIAS; + $selectFields = []; + + foreach ($selects as $select) { + $selectAlias = $query->getFkSelectAliases()[$select]; + $fieldAlias = substr($select, strrpos($select, '.') + 1); + $selectFields[$fieldAlias] = $selectAlias; + } + + $firstSelect = $selects[0]; + $pathParts = explode('.', $firstSelect); + $numParts = count($pathParts); + $parentAlias = $numParts > 2 ? $pathParts[$numParts - 3] : $mainAlias; + + $selectFields['id'] = sprintf('%s.id', $alias); + $selectFields['_parent_id'] = $parentAlias . '.id'; + $selectFields['_base_id'] = $mainAlias . '.id'; + + return $selectFields; + } + + /** + * @param array $selects + * @param \Civi\Api4\Query\Api4SelectQuery $query + * + * @return array + */ + private function runWithNewSelects(array $selects, Api4SelectQuery $query) { + $aliasedSelects = array_map(function ($field, $alias) { + return sprintf('%s as "%s"', $field, $alias); + }, $selects, array_keys($selects)); + + $newSelect = sprintf('SELECT DISTINCT %s', implode(", ", $aliasedSelects)); + $sql = str_replace("\n", ' ', $query->getQuery()->toSQL()); + $originalSelect = substr($sql, 0, strpos($sql, ' FROM')); + $sql = str_replace($originalSelect, $newSelect, $sql); + + $relatedResults = []; + $resultDAO = \CRM_Core_DAO::executeQuery($sql); + while ($resultDAO->fetch()) { + $relatedResult = []; + foreach ($selects as $alias => $column) { + $returnName = $alias; + $alias = str_replace('.', '_', $alias); + if (property_exists($resultDAO, $alias)) { + $relatedResult[$returnName] = $resultDAO->$alias; + } + }; + $relatedResults[] = $relatedResult; + } + + return $relatedResults; + } + + /** + * @param \Civi\Api4\Query\Api4SelectQuery $query + * @param $alias + * @param $selects + * @return array + */ + protected function getJoinResults(Api4SelectQuery $query, $alias, $selects) { + $apiFieldSpec = $query->getApiFieldSpec(); + if (!empty($apiFieldSpec[$alias]['serialize'])) { + $type = $apiFieldSpec[$alias]['serialize']; + $joinResults = $this->getResultsForSerializedField($selects, $type, $query); + } + else { + $joinResults = $this->runWithNewSelects($selects, $query); + } + + // Remove results with no matching entries + $joinResults = array_filter($joinResults, function ($result) { + return !empty($result['id']); + }); + + return $joinResults; + } + + /** + * Get all the option_value values required in the query + * + * @param array $selects + * @param \Civi\Api4\Query\Api4SelectQuery $query + * @param array $values + * + * @return array + */ + private function getOptionValuesFromValues( + array $selects, + Api4SelectQuery $query, + array $values + ) { + $sampleField = current($selects); + $alias = strstr($sampleField, '.', TRUE); + + // Get the option value table that was joined + $relatedTable = NULL; + foreach ($query->getJoinedTables() as $joinedTable) { + if ($joinedTable->getAlias() === $alias) { + $relatedTable = $joinedTable; + } + } + + // We only want subselects related to the joined table + $subSelects = array_filter($selects, function ($select) use ($alias) { + return strpos($select, $alias) === 0; + }); + + // Fetch all related option_value entries + $valueField = $alias . '.value'; + $subSelects[] = $valueField; + $tableName = $relatedTable->getTargetTable(); + $conditions = $relatedTable->getExtraJoinConditions(); + $conditions[] = $valueField . ' IN ("' . implode('", "', $values) . '")'; + $subQuery = new \CRM_Utils_SQL_Select($tableName . ' ' . $alias); + $subQuery->where($conditions); + $subQuery->select($subSelects); + $subResults = $subQuery->execute()->fetchAll(); + + return $subResults; + } + +} diff --git a/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php b/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php new file mode 100644 index 000000000000..2e7faab588bc --- /dev/null +++ b/Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php @@ -0,0 +1,100 @@ +getApiRequest(); + if (is_a($apiRequest, 'Civi\Api4\Generic\AbstractAction')) { + $paramInfo = $apiRequest->getParamInfo(); + foreach ($paramInfo as $param => $info) { + $getParam = 'get' . ucfirst($param); + $value = $apiRequest->$getParam(); + // Required fields + if (!empty($info['required']) && (!$value && $value !== 0 && $value !== '0')) { + throw new \API_Exception('Parameter "' . $param . '" is required.'); + } + if (!empty($info['type']) && !self::checkType($value, $info['type'])) { + throw new \API_Exception('Parameter "' . $param . '" is not of the correct type. Expecting ' . implode(' or ', $info['type']) . '.'); + } + } + } + } + + /** + * Validate variable type on input + * + * @param $value + * @param $types + * @return bool + * @throws \API_Exception + */ + public static function checkType($value, $types) { + if ($value === NULL) { + return TRUE; + } + foreach ($types as $type) { + switch ($type) { + case 'array': + case 'bool': + case 'string': + case 'object': + $tester = 'is_' . $type; + if ($tester($value)) { + return TRUE; + } + break; + + case 'int': + if (\CRM_Utils_Rule::integer($value)) { + return TRUE; + } + break; + + case 'mixed': + return TRUE; + + default: + throw new \API_Exception('Unknown parameter type: ' . $type); + } + } + return FALSE; + } + +} diff --git a/Civi/Api4/Generic/AbstractAction.php b/Civi/Api4/Generic/AbstractAction.php new file mode 100644 index 000000000000..1a67e01eaf5b --- /dev/null +++ b/Civi/Api4/Generic/AbstractAction.php @@ -0,0 +1,481 @@ +setValue('first_name', 'Hello') + * ->addChain('add_to_a_group', GroupContact::create()->setValue('contact_id', '$id')->setValue('group_id', 123)) + * + * This will substitute the id of the newly created contact with $id. + * + * @var array + */ + protected $chain = []; + + /** + * Whether to enforce acl permissions based on the current user. + * + * Setting to FALSE will disable permission checks and override ACLs. + * In REST/javascript this cannot be disabled. + * + * @var bool + */ + protected $checkPermissions = TRUE; + + /** + * @var string + */ + protected $_entityName; + + /** + * @var string + */ + protected $_actionName; + + /** + * @var \ReflectionClass + */ + private $_reflection; + + /** + * @var array + */ + private $_paramInfo; + + /** + * @var array + */ + private $_entityFields; + + /** + * @var array + */ + private $_arrayStorage = []; + + /** + * @var int + * Used to identify api calls for transactions + * @see \Civi\Core\Transaction\Manager + */ + private $_id; + + /** + * Action constructor. + * + * @param string $entityName + * @param string $actionName + * @throws \API_Exception + */ + public function __construct($entityName, $actionName) { + // If a namespaced class name is passed in + if (strpos($entityName, '\\') !== FALSE) { + $entityName = substr($entityName, strrpos($entityName, '\\') + 1); + } + $this->_entityName = $entityName; + $this->_actionName = $actionName; + $this->_id = \Civi\API\Request::getNextId(); + } + + /** + * Strictly enforce api parameters + * @param $name + * @param $value + * @throws \Exception + */ + public function __set($name, $value) { + throw new \API_Exception('Unknown api parameter'); + } + + /** + * @param int $val + * @return $this + * @throws \API_Exception + */ + public function setVersion($val) { + if ($val != 4) { + throw new \API_Exception('Cannot modify api version'); + } + return $this; + } + + /** + * @param string $name + * Unique name for this chained request + * @param \Civi\Api4\Generic\AbstractAction $apiRequest + * @param string|int $index + * Either a string for how the results should be indexed e.g. 'name' + * or the index of a single result to return e.g. 0 for the first result. + * @return $this + */ + public function addChain($name, AbstractAction $apiRequest, $index = NULL) { + $this->chain[$name] = [$apiRequest->getEntityName(), $apiRequest->getActionName(), $apiRequest->getParams(), $index]; + return $this; + } + + /** + * Magic function to provide addFoo, getFoo and setFoo for params. + * + * @param $name + * @param $arguments + * @return static|mixed + * @throws \API_Exception + */ + public function __call($name, $arguments) { + $param = lcfirst(substr($name, 3)); + if (!$param || $param[0] == '_') { + throw new \API_Exception('Unknown api parameter: ' . $name); + } + $mode = substr($name, 0, 3); + // Handle plural when adding to e.g. $values with "addValue" method. + if ($mode == 'add' && $this->paramExists($param . 's')) { + $param .= 's'; + } + if ($this->paramExists($param)) { + switch ($mode) { + case 'get': + return $this->$param; + + case 'set': + $this->$param = $arguments[0]; + return $this; + + case 'add': + if (!is_array($this->$param)) { + throw new \API_Exception('Cannot add to non-array param'); + } + if (array_key_exists(1, $arguments)) { + $this->{$param}[$arguments[0]] = $arguments[1]; + } + else { + $this->{$param}[] = $arguments[0]; + } + return $this; + } + } + throw new \API_Exception('Unknown api parameter: ' . $name); + } + + /** + * Invoke api call. + * + * At this point all the params have been sent in and we initiate the api call & return the result. + * This is basically the outer wrapper for api v4. + * + * @return \Civi\Api4\Generic\Result + * @throws \Civi\API\Exception\UnauthorizedException + */ + public function execute() { + /** @var \Civi\API\Kernel $kernel */ + $kernel = \Civi::service('civi_api_kernel'); + + return $kernel->runRequest($this); + } + + /** + * @param \Civi\Api4\Generic\Result $result + */ + abstract public function _run(Result $result); + + /** + * Serialize this object's params into an array + * @return array + */ + public function getParams() { + $params = []; + foreach ($this->reflect()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) { + $name = $property->getName(); + // Skip variables starting with an underscore + if ($name[0] != '_') { + $params[$name] = $this->$name; + } + } + return $params; + } + + /** + * Get documentation for one or all params + * + * @param string $param + * @return array of arrays [description, type, default, (comment)] + */ + public function getParamInfo($param = NULL) { + if (!isset($this->_paramInfo)) { + $defaults = $this->getParamDefaults(); + foreach ($this->reflect()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) { + $name = $property->getName(); + if ($name != 'version' && $name[0] != '_') { + $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property'); + $this->_paramInfo[$name]['default'] = $defaults[$name]; + } + } + } + return $param ? $this->_paramInfo[$param] : $this->_paramInfo; + } + + /** + * @return string + */ + public function getEntityName() { + return $this->_entityName; + } + + /** + * + * @return string + */ + public function getActionName() { + return $this->_actionName; + } + + /** + * @param string $param + * @return bool + */ + public function paramExists($param) { + return array_key_exists($param, $this->getParams()); + } + + /** + * @return array + */ + protected function getParamDefaults() { + return array_intersect_key($this->reflect()->getDefaultProperties(), $this->getParams()); + } + + /** + * @inheritDoc + */ + public function offsetExists($offset) { + return in_array($offset, ['entity', 'action', 'params', 'version', 'check_permissions', 'id']) || isset($this->_arrayStorage[$offset]); + } + + /** + * @inheritDoc + */ + public function &offsetGet($offset) { + $val = NULL; + if (in_array($offset, ['entity', 'action'])) { + $offset .= 'Name'; + } + if (in_array($offset, ['entityName', 'actionName', 'params', 'version'])) { + $getter = 'get' . ucfirst($offset); + $val = $this->$getter(); + return $val; + } + if ($offset == 'check_permissions') { + return $this->checkPermissions; + } + if ($offset == 'id') { + return $this->_id; + } + if (isset($this->_arrayStorage[$offset])) { + return $this->_arrayStorage[$offset]; + } + return $val; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value) { + if (in_array($offset, ['entity', 'action', 'entityName', 'actionName', 'params', 'version', 'id'])) { + throw new \API_Exception('Cannot modify api4 state via array access'); + } + if ($offset == 'check_permissions') { + $this->setCheckPermissions($value); + } + else { + $this->_arrayStorage[$offset] = $value; + } + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset) { + if (in_array($offset, ['entity', 'action', 'entityName', 'actionName', 'params', 'check_permissions', 'version', 'id'])) { + throw new \API_Exception('Cannot modify api4 state via array access'); + } + unset($this->_arrayStorage[$offset]); + } + + /** + * Is this api call permitted? + * + * This function is called if checkPermissions is set to true. + * + * @return bool + */ + public function isAuthorized() { + $permissions = $this->getPermissions(); + return \CRM_Core_Permission::check($permissions); + } + + /** + * @return array + */ + public function getPermissions() { + $permissions = call_user_func(["\\Civi\\Api4\\" . $this->_entityName, 'permissions']); + $permissions += [ + // applies to getFields, getActions, etc. + 'meta' => ['access CiviCRM'], + // catch-all, applies to create, get, delete, etc. + 'default' => ['administer CiviCRM'], + ]; + $action = $this->getActionName(); + if (isset($permissions[$action])) { + return $permissions[$action]; + } + elseif (in_array($action, ['getActions', 'getFields'])) { + return $permissions['meta']; + } + return $permissions['default']; + } + + /** + * Returns schema fields for this entity & action. + * + * Here we bypass the api wrapper and execute the getFields action directly. + * This is because we DON'T want the wrapper to check permissions as this is an internal op, + * but we DO want permissions to be checked inside the getFields request so e.g. the api_key + * field can be conditionally included. + * @see \Civi\Api4\Action\Contact\GetFields + * + * @return array + */ + public function entityFields() { + if (!$this->_entityFields) { + $getFields = ActionUtil::getAction($this->getEntityName(), 'getFields'); + $result = new Result(); + if (method_exists($this, 'getBaoName')) { + $getFields->setIncludeCustom(FALSE); + } + $getFields + ->setCheckPermissions($this->checkPermissions) + ->setAction($this->getActionName()) + ->_run($result); + $this->_entityFields = (array) $result->indexBy('name'); + } + return $this->_entityFields; + } + + /** + * @return \ReflectionClass + */ + public function reflect() { + if (!$this->_reflection) { + $this->_reflection = new \ReflectionClass($this); + } + return $this->_reflection; + } + + /** + * Validates required fields for actions which create a new object. + * + * @param $values + * @return array + * @throws \API_Exception + */ + protected function checkRequiredFields($values) { + $unmatched = []; + foreach ($this->entityFields() as $fieldName => $fieldInfo) { + if (!isset($values[$fieldName]) || $values[$fieldName] === '') { + if (!empty($fieldInfo['required']) && !isset($fieldInfo['default_value'])) { + $unmatched[] = $fieldName; + } + elseif (!empty($fieldInfo['required_if'])) { + if ($this->evaluateCondition($fieldInfo['required_if'], ['values' => $values])) { + $unmatched[] = $fieldName; + } + } + } + } + return $unmatched; + } + + /** + * This function is used internally for evaluating field annotations. + * + * It should never be passed raw user input. + * + * @param string $expr + * Conditional in php format e.g. $foo > $bar + * @param array $vars + * Variable name => value + * @return bool + * @throws \API_Exception + * @throws \Exception + */ + protected function evaluateCondition($expr, $vars) { + if (strpos($expr, '}') !== FALSE || strpos($expr, '{') !== FALSE) { + throw new \API_Exception('Illegal character in expression'); + } + $tpl = "{if $expr}1{else}0{/if}"; + return (bool) trim(\CRM_Core_Smarty::singleton()->fetchWith('string:' . $tpl, $vars)); + } + +} diff --git a/Civi/Api4/Generic/AbstractBatchAction.php b/Civi/Api4/Generic/AbstractBatchAction.php new file mode 100644 index 000000000000..6250e256e1d5 --- /dev/null +++ b/Civi/Api4/Generic/AbstractBatchAction.php @@ -0,0 +1,99 @@ +select = (array) $select; + parent::__construct($entityName, $actionName); + } + + /** + * @return array + */ + protected function getBatchRecords() { + $params = [ + 'checkPermissions' => $this->checkPermissions, + 'where' => $this->where, + 'orderBy' => $this->orderBy, + 'limit' => $this->limit, + 'offset' => $this->offset, + ]; + if (empty($this->reload)) { + $params['select'] = $this->select; + } + + return (array) civicrm_api4($this->getEntityName(), 'get', $params); + } + + /** + * @return array + */ + protected function getSelect() { + return $this->select; + } + +} diff --git a/Civi/Api4/Generic/AbstractCreateAction.php b/Civi/Api4/Generic/AbstractCreateAction.php new file mode 100644 index 000000000000..2b1e3a3fd158 --- /dev/null +++ b/Civi/Api4/Generic/AbstractCreateAction.php @@ -0,0 +1,77 @@ + value pairs. + * @method $this addValue($field, $value) Set field value. + * @method array getValues() Get field values. + * + * @package Civi\Api4\Generic + */ +abstract class AbstractCreateAction extends AbstractAction { + + /** + * Field values to set + * + * @var array + */ + protected $values = []; + + /** + * @param string $key + * + * @return mixed|null + */ + public function getValue($key) { + return isset($this->values[$key]) ? $this->values[$key] : NULL; + } + + /** + * @throws \API_Exception + */ + protected function validateValues() { + $unmatched = $this->checkRequiredFields($this->getValues()); + if ($unmatched) { + throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); + } + } + +} diff --git a/Civi/Api4/Generic/AbstractEntity.php b/Civi/Api4/Generic/AbstractEntity.php new file mode 100644 index 000000000000..2101213ef255 --- /dev/null +++ b/Civi/Api4/Generic/AbstractEntity.php @@ -0,0 +1,124 @@ +select = ['row_count']; + return $this; + } + + /** + * Adds field defaults to the where clause. + * + * Note: it will skip adding field defaults when fetching records by id, + * or if that field has already been added to the where clause. + * + * @throws \API_Exception + */ + protected function setDefaultWhereClause() { + if (!$this->_itemsToGet('id')) { + $fields = $this->entityFields(); + foreach ($fields as $field) { + if (isset($field['default_value']) && !$this->_whereContains($field['name'])) { + $this->addWhere($field['name'], '=', $field['default_value']); + } + } + } + } + + /** + * Helper to parse the WHERE param for getRecords to perform simple pre-filtering. + * + * This is intended to optimize some common use-cases e.g. calling the api to get + * one or more records by name or id. + * + * Ex: If getRecords fetches a long list of items each with a unique name, + * but the user has specified a single record to retrieve, you can optimize the call + * by checking $this->_itemsToGet('name') and only fetching the item(s) with that name. + * + * @param string $field + * @return array|null + */ + protected function _itemsToGet($field) { + foreach ($this->where as $clause) { + // Look for exact-match operators (=, IN, or LIKE with no wildcard) + if ($clause[0] == $field && (in_array($clause[1], ['=', 'IN']) || ($clause[1] == 'LIKE' && !(is_string($clause[2]) && strpos($clause[2], '%') !== FALSE)))) { + return (array) $clause[2]; + } + } + return NULL; + } + + /** + * Helper to see if a field should be selected by the getRecords function. + * + * Checks the SELECT, WHERE and ORDER BY params to see what fields are needed. + * + * Note that if no SELECT clause has been set then all fields should be selected + * and this function will always return TRUE. + * + * @param string $field + * @return bool + */ + protected function _isFieldSelected($field) { + if (!$this->select || in_array($field, $this->select) || isset($this->orderBy[$field])) { + return TRUE; + } + return $this->_whereContains($field); + } + + /** + * Walk through the where clause and check if a field is in use. + * + * @param string $field + * @param array $clauses + * @return bool + */ + protected function _whereContains($field, $clauses = NULL) { + if ($clauses === NULL) { + $clauses = $this->where; + } + foreach ($clauses as $clause) { + if (is_array($clause) && is_string($clause[0])) { + if ($clause[0] == $field) { + return TRUE; + } + elseif (is_array($clause[1])) { + return $this->_whereContains($field, $clause[1]); + } + } + } + return FALSE; + } + +} diff --git a/Civi/Api4/Generic/AbstractQueryAction.php b/Civi/Api4/Generic/AbstractQueryAction.php new file mode 100644 index 000000000000..3e3dc2278ef3 --- /dev/null +++ b/Civi/Api4/Generic/AbstractQueryAction.php @@ -0,0 +1,179 @@ +addWhere('contact_type', 'IN', array('Individual', 'Household')) + * + * @var array + */ + protected $where = []; + + /** + * Array of field(s) to use in ordering the results + * + * Defaults to id ASC + * + * $example->addOrderBy('sort_name', 'ASC') + * + * @var array + */ + protected $orderBy = []; + + /** + * Maximum number of results to return. + * + * Defaults to unlimited. + * + * Note: the Api Explorer sets this to 25 by default to avoid timeouts. + * Change or remove this default for your application code. + * + * @var int + */ + protected $limit = 0; + + /** + * Zero-based index of first result to return. + * + * Defaults to "0" - first record. + * + * @var int + */ + protected $offset = 0; + + /** + * @param string $field + * @param string $op + * @param mixed $value + * @return $this + * @throws \API_Exception + */ + public function addWhere($field, $op, $value = NULL) { + if (!in_array($op, \CRM_Core_DAO::acceptedSQLOperators())) { + throw new \API_Exception('Unsupported operator'); + } + $this->where[] = [$field, $op, $value]; + return $this; + } + + /** + * Adds one or more AND/OR/NOT clause groups + * + * @param string $operator + * @param mixed $condition1 ... $conditionN + * Either a nested array of arguments, or a variable number of arguments passed to this function. + * + * @return $this + * @throws \API_Exception + */ + public function addClause($operator, $condition1) { + if (!is_array($condition1[0])) { + $condition1 = array_slice(func_get_args(), 1); + } + $this->where[] = [$operator, $condition1]; + return $this; + } + + /** + * @param string $field + * @param string $direction + * @return $this + */ + public function addOrderBy($field, $direction = 'ASC') { + $this->orderBy[$field] = $direction; + return $this; + } + + /** + * A human-readable where clause, for the reading enjoyment of you humans. + * + * @param array $whereClause + * @param string $op + * @return string + */ + protected function whereClauseToString($whereClause = NULL, $op = 'AND') { + if ($whereClause === NULL) { + $whereClause = $this->where; + } + $output = ''; + if (!is_array($whereClause) || !$whereClause) { + return $output; + } + if (in_array($whereClause[0], ['AND', 'OR', 'NOT'])) { + $op = array_shift($whereClause); + if ($op == 'NOT') { + $output = 'NOT '; + $op = 'AND'; + } + return $output . '(' . $this->whereClauseToString($whereClause, $op) . ')'; + } + elseif (isset($whereClause[1]) && in_array($whereClause[1], \CRM_Core_DAO::acceptedSQLOperators())) { + $output = $whereClause[0] . ' ' . $whereClause[1] . ' '; + if (isset($whereClause[2])) { + $output .= is_array($whereClause[2]) ? '[' . implode(', ', $whereClause[2]) . ']' : $whereClause[2]; + } + } + else { + $clauses = []; + foreach (array_filter($whereClause) as $clause) { + $clauses[] = $this->whereClauseToString($clause, $op); + } + $output = implode(" $op ", $clauses); + } + return $output; + } + +} diff --git a/Civi/Api4/Generic/AbstractSaveAction.php b/Civi/Api4/Generic/AbstractSaveAction.php new file mode 100644 index 000000000000..30759ee620d3 --- /dev/null +++ b/Civi/Api4/Generic/AbstractSaveAction.php @@ -0,0 +1,125 @@ +idField = array_values((array) $idField)[0]; + parent::__construct($entityName, $actionName); + } + + /** + * @throws \API_Exception + */ + protected function validateValues() { + $unmatched = []; + foreach ($this->records as $record) { + if (empty($record[$this->idField])) { + $unmatched = array_unique(array_merge($unmatched, $this->checkRequiredFields($record))); + } + } + if ($unmatched) { + throw new \API_Exception("Mandatory values missing from Api4 {$this->getEntityName()}::{$this->getActionName()}: " . implode(", ", $unmatched), "mandatory_missing", ["fields" => $unmatched]); + } + } + + /** + * @return string + */ + protected function getIdField() { + return $this->idField; + } + +} diff --git a/Civi/Api4/Generic/AbstractUpdateAction.php b/Civi/Api4/Generic/AbstractUpdateAction.php new file mode 100644 index 000000000000..371b8c76efeb --- /dev/null +++ b/Civi/Api4/Generic/AbstractUpdateAction.php @@ -0,0 +1,80 @@ + value pairs. + * @method $this addValue($field, $value) Set field value. + * @method array getValues() Get field values. + * @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving. + * @method bool getReload() + * + * @package Civi\Api4\Generic + */ +abstract class AbstractUpdateAction extends AbstractBatchAction { + + /** + * Field values to update. + * + * @var array + * @required + */ + protected $values = []; + + /** + * Reload objects after saving. + * + * Setting to TRUE will load complete records and return them as the api result. + * If FALSE the api usually returns only the fields specified to be updated. + * + * @var bool + */ + protected $reload = FALSE; + + /** + * @param string $key + * + * @return mixed|null + */ + public function getValue($key) { + return isset($this->values[$key]) ? $this->values[$key] : NULL; + } + +} diff --git a/Civi/Api4/Generic/BasicBatchAction.php b/Civi/Api4/Generic/BasicBatchAction.php new file mode 100644 index 000000000000..fb54c7647f06 --- /dev/null +++ b/Civi/Api4/Generic/BasicBatchAction.php @@ -0,0 +1,108 @@ + array + */ + private $doer; + + /** + * BasicBatchAction constructor. + * + * @param string $entityName + * @param string $actionName + * @param string|array $select + * One or more fields to select from each matching item. + * @param callable $doer + * Function(array $item, BasicBatchAction $thisAction) => array + */ + public function __construct($entityName, $actionName, $select = 'id', $doer = NULL) { + parent::__construct($entityName, $actionName, $select); + $this->doer = $doer; + } + + /** + * We pass the doTask function an array representing one item to update. + * We expect to get the same format back. + * + * @param \Civi\Api4\Generic\Result $result + */ + public function _run(Result $result) { + foreach ($this->getBatchRecords() as $item) { + $result[] = $this->doTask($item); + } + } + + /** + * This Basic Batch class can be used in one of two ways: + * + * 1. Use this class directly by passing a callable ($doer) to the constructor. + * 2. Extend this class and override this function. + * + * Either way, this function should return an array with an output record + * for the item. + * + * @param array $item + * @return array + * @throws \Civi\API\Exception\NotImplementedException + */ + protected function doTask($item) { + if (is_callable($this->doer)) { + return call_user_func($this->doer, $item, $this); + } + throw new NotImplementedException('Doer function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName()); + } + +} diff --git a/Civi/Api4/Generic/BasicCreateAction.php b/Civi/Api4/Generic/BasicCreateAction.php new file mode 100644 index 000000000000..43cc8bb19318 --- /dev/null +++ b/Civi/Api4/Generic/BasicCreateAction.php @@ -0,0 +1,99 @@ + array + */ + private $setter; + + /** + * Basic Create constructor. + * + * @param string $entityName + * @param string $actionName + * @param callable $setter + * Function(array $item, BasicCreateAction $thisAction) => array + */ + public function __construct($entityName, $actionName, $setter = NULL) { + parent::__construct($entityName, $actionName); + $this->setter = $setter; + } + + /** + * We pass the writeRecord function an array representing one item to write. + * We expect to get the same format back. + * + * @param \Civi\Api4\Generic\Result $result + */ + public function _run(Result $result) { + $this->validateValues(); + $result->exchangeArray([$this->writeRecord($this->values)]); + } + + /** + * This Basic Create class can be used in one of two ways: + * + * 1. Use this class directly by passing a callable ($setter) to the constructor. + * 2. Extend this class and override this function. + * + * Either way, this function should return an array representing the one new object. + * + * @param array $item + * @return array + * @throws \Civi\API\Exception\NotImplementedException + */ + protected function writeRecord($item) { + if (is_callable($this->setter)) { + return call_user_func($this->setter, $item, $this); + } + throw new NotImplementedException('Setter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName()); + } + +} diff --git a/Civi/Api4/Generic/BasicGetAction.php b/Civi/Api4/Generic/BasicGetAction.php new file mode 100644 index 000000000000..5e5dfb19a234 --- /dev/null +++ b/Civi/Api4/Generic/BasicGetAction.php @@ -0,0 +1,119 @@ + array + */ + private $getter; + + /** + * Basic Get constructor. + * + * @param string $entityName + * @param string $actionName + * @param callable $getter + */ + public function __construct($entityName, $actionName, $getter = NULL) { + parent::__construct($entityName, $actionName); + $this->getter = $getter; + } + + /** + * Fetch results from the getter then apply filter/sort/select/limit. + * + * @param \Civi\Api4\Generic\Result $result + */ + public function _run(Result $result) { + $this->setDefaultWhereClause(); + $values = $this->getRecords(); + $result->exchangeArray($this->queryArray($values)); + } + + /** + * This Basic Get class is a general-purpose api for non-DAO-based entities. + * + * Useful for fetching records from files or other places. + * You can specify any php function to retrieve the records, and this class will + * automatically filter, sort, select & limit the raw data from your callback. + * + * You can implement this action in one of two ways: + * 1. Use this class directly by passing a callable ($getter) to the constructor. + * 2. Extend this class and override this function. + * + * Either way, this function should return an array of arrays, each representing one retrieved object. + * + * The simplest thing for your getter function to do is return every full record + * and allow this class to automatically do the sorting and filtering. + * + * Sometimes however that may not be practical for performance reasons. + * To optimize your getter, it can use the following helpers from $this: + * + * Use this->_itemsToGet() to match records to field values in the WHERE clause. + * Note the WHERE clause can potentially be very complex and it is not recommended + * to parse $this->where yourself. + * + * Use $this->_isFieldSelected() to check if a field value is called for - useful + * if loading the field involves expensive calculations. + * + * Be careful not to make assumptions, e.g. if LIMIT 100 is specified and your getter "helpfully" truncates the list + * at 100 without accounting for WHERE, ORDER BY and LIMIT clauses, the final filtered result may be very incorrect. + * + * @return array + * @throws \Civi\API\Exception\NotImplementedException + */ + protected function getRecords() { + if (is_callable($this->getter)) { + return call_user_func($this->getter, $this); + } + throw new NotImplementedException('Getter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName()); + } + +} diff --git a/Civi/Api4/Generic/BasicGetFieldsAction.php b/Civi/Api4/Generic/BasicGetFieldsAction.php new file mode 100644 index 000000000000..511c0633075b --- /dev/null +++ b/Civi/Api4/Generic/BasicGetFieldsAction.php @@ -0,0 +1,191 @@ +getEntityName(), $this->getAction()); + } + catch (NotImplementedException $e) { + } + if (isset($actionClass) && method_exists($actionClass, 'fields')) { + $values = $actionClass->fields(); + } + else { + $values = $this->getRecords(); + } + $this->padResults($values); + $result->exchangeArray($this->queryArray($values)); + } + + /** + * Ensure every result contains, at minimum, the array keys as defined in $this->fields. + * + * Attempt to set some sensible defaults for some fields. + * + * In most cases it's not necessary to override this function, even if your entity is really weird. + * Instead just override $this->fields and thes function will respect that. + * + * @param array $values + */ + protected function padResults(&$values) { + $fields = array_column($this->fields(), 'name'); + foreach ($values as &$field) { + $defaults = array_intersect_key([ + 'title' => empty($field['name']) ? NULL : ucwords(str_replace('_', ' ', $field['name'])), + 'entity' => $this->getEntityName(), + 'required' => FALSE, + 'options' => !empty($field['pseudoconstant']), + 'data_type' => \CRM_Utils_Array::value('type', $field, 'String'), + ], array_flip($fields)); + $field += $defaults; + if (!$this->loadOptions && isset($defaults['options'])) { + $field['options'] = (bool) $field['options']; + } + $field += array_fill_keys($fields, NULL); + } + } + + /** + * @return string + */ + public function getAction() { + // For actions that build on top of other actions, return fields for the simpler action + $sub = [ + 'save' => 'create', + 'replace' => 'create', + ]; + return $sub[$this->action] ?? $this->action; + } + + public function fields() { + return [ + [ + 'name' => 'name', + 'data_type' => 'String', + ], + [ + 'name' => 'title', + 'data_type' => 'String', + ], + [ + 'name' => 'description', + 'data_type' => 'String', + ], + [ + 'name' => 'default_value', + 'data_type' => 'String', + ], + [ + 'name' => 'required', + 'data_type' => 'Boolean', + ], + [ + 'name' => 'required_if', + 'data_type' => 'String', + ], + [ + 'name' => 'options', + 'data_type' => 'Array', + ], + [ + 'name' => 'data_type', + 'data_type' => 'String', + ], + [ + 'name' => 'input_type', + 'data_type' => 'String', + ], + [ + 'name' => 'input_attrs', + 'data_type' => 'Array', + ], + [ + 'name' => 'fk_entity', + 'data_type' => 'String', + ], + [ + 'name' => 'serialize', + 'data_type' => 'Integer', + ], + [ + 'name' => 'entity', + 'data_type' => 'String', + ], + ]; + } + +} diff --git a/Civi/Api4/Generic/BasicReplaceAction.php b/Civi/Api4/Generic/BasicReplaceAction.php new file mode 100644 index 000000000000..0d85c6aac978 --- /dev/null +++ b/Civi/Api4/Generic/BasicReplaceAction.php @@ -0,0 +1,149 @@ +getBatchRecords(); + + // Copy defaults from where clause if the operator is = + foreach ($this->where as $clause) { + if (is_array($clause) && $clause[1] === '=') { + $this->defaults[$clause[0]] = $clause[2]; + } + } + + $idField = $this->getSelect()[0]; + $toDelete = array_diff_key(array_column($items, NULL, $idField), array_flip(array_filter(\CRM_Utils_Array::collect($idField, $this->records)))); + + // Try to delegate to the Save action + try { + $saveAction = ActionUtil::getAction($this->getEntityName(), 'save'); + $saveAction + ->setCheckPermissions($this->getCheckPermissions()) + ->setReload($this->reload) + ->setRecords($this->records) + ->setDefaults($this->defaults); + $result->exchangeArray((array) $saveAction->execute()); + } + // Fall back on Create/Update if Save doesn't exist + catch (NotImplementedException $e) { + foreach ($this->records as $record) { + $record += $this->defaults; + if (!empty($record[$idField])) { + $result[] = civicrm_api4($this->getEntityName(), 'update', [ + 'reload' => $this->reload, + 'where' => [[$idField, '=', $record[$idField]]], + 'values' => $record, + 'checkPermissions' => $this->getCheckPermissions(), + ])->first(); + } + else { + $result[] = civicrm_api4($this->getEntityName(), 'create', [ + 'values' => $record, + 'checkPermissions' => $this->getCheckPermissions(), + ])->first(); + } + } + } + + if ($toDelete) { + $result->deleted = (array) civicrm_api4($this->getEntityName(), 'delete', [ + 'where' => [[$idField, 'IN', array_keys($toDelete)]], + 'checkPermissions' => $this->getCheckPermissions(), + ]); + } + } + +} diff --git a/Civi/Api4/Generic/BasicSaveAction.php b/Civi/Api4/Generic/BasicSaveAction.php new file mode 100644 index 000000000000..cd63ca21d1a6 --- /dev/null +++ b/Civi/Api4/Generic/BasicSaveAction.php @@ -0,0 +1,114 @@ + array + */ + private $setter; + + /** + * Basic Create constructor. + * + * @param string $entityName + * @param string $actionName + * @param string $idField + * @param callable $setter + * Function(array $item, BasicCreateAction $thisAction) => array + */ + public function __construct($entityName, $actionName, $idField = 'id', $setter = NULL) { + parent::__construct($entityName, $actionName, $idField); + $this->setter = $setter; + } + + /** + * We pass the writeRecord function an array representing one item to write. + * We expect to get the same format back. + * + * @param \Civi\Api4\Generic\Result $result + */ + public function _run(Result $result) { + $this->validateValues(); + foreach ($this->records as $record) { + $record += $this->defaults; + $result[] = $this->writeRecord($record); + } + if ($this->reload) { + /** @var BasicGetAction $get */ + $get = ActionUtil::getAction($this->getEntityName(), 'get'); + $get + ->setCheckPermissions($this->getCheckPermissions()) + ->addWhere($this->getIdField(), 'IN', (array) $result->column($this->getIdField())); + $result->exchangeArray((array) $get->execute()); + } + } + + /** + * This Basic Save class can be used in one of two ways: + * + * 1. Use this class directly by passing a callable ($setter) to the constructor. + * 2. Extend this class and override this function. + * + * Either way, this function should return an array representing the one new object. + * + * @param array $item + * @return array + * @throws \Civi\API\Exception\NotImplementedException + */ + protected function writeRecord($item) { + if (is_callable($this->setter)) { + return call_user_func($this->setter, $item, $this); + } + throw new NotImplementedException('Setter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName()); + } + +} diff --git a/Civi/Api4/Generic/BasicUpdateAction.php b/Civi/Api4/Generic/BasicUpdateAction.php new file mode 100644 index 000000000000..c3aa0c1fb6af --- /dev/null +++ b/Civi/Api4/Generic/BasicUpdateAction.php @@ -0,0 +1,108 @@ + array + */ + private $setter; + + /** + * BasicUpdateAction constructor. + * + * @param string $entityName + * @param string $actionName + * @param string|array $select + * One or more fields to select from each matching item. + * @param callable $setter + * Function(array $item, BasicUpdateAction $thisAction) => array + */ + public function __construct($entityName, $actionName, $select = 'id', $setter = NULL) { + parent::__construct($entityName, $actionName, $select); + $this->setter = $setter; + } + + /** + * We pass the writeRecord function an array representing one item to update. + * We expect to get the same format back. + * + * @param \Civi\Api4\Generic\Result $result + * @throws \API_Exception + * @throws \Civi\API\Exception\NotImplementedException + */ + public function _run(Result $result) { + foreach ($this->getBatchRecords() as $item) { + $result[] = $this->writeRecord($this->values + $item); + } + + if (!$result->count()) { + throw new \API_Exception('Cannot ' . $this->getActionName() . ' ' . $this->getEntityName() . ', no records found with ' . $this->whereClauseToString()); + } + } + + /** + * This Basic Update class can be used in one of two ways: + * + * 1. Use this class directly by passing a callable ($setter) to the constructor. + * 2. Extend this class and override this function. + * + * Either way, this function should return an array representing the one modified object. + * + * @param array $item + * @return array + * @throws \Civi\API\Exception\NotImplementedException + */ + protected function writeRecord($item) { + if (is_callable($this->setter)) { + return call_user_func($this->setter, $item, $this); + } + throw new NotImplementedException('Setter function not found for api4 ' . $this->getEntityName() . '::' . $this->getActionName()); + } + +} diff --git a/Civi/Api4/Generic/DAOCreateAction.php b/Civi/Api4/Generic/DAOCreateAction.php new file mode 100644 index 000000000000..e3d63c2cd333 --- /dev/null +++ b/Civi/Api4/Generic/DAOCreateAction.php @@ -0,0 +1,71 @@ +validateValues(); + $params = $this->values; + $this->fillDefaults($params); + + $resultArray = $this->writeObjects([$params]); + + $result->exchangeArray($resultArray); + } + + /** + * @throws \API_Exception + */ + protected function validateValues() { + if (!empty($this->values['id'])) { + throw new \API_Exception('Cannot pass id to Create action. Use Update action instead.'); + } + parent::validateValues(); + } + +} diff --git a/Civi/Api4/Generic/DAODeleteAction.php b/Civi/Api4/Generic/DAODeleteAction.php new file mode 100644 index 000000000000..8001b7e848e2 --- /dev/null +++ b/Civi/Api4/Generic/DAODeleteAction.php @@ -0,0 +1,110 @@ +getParamDefaults(); + if ($defaults['where'] && !array_diff_key($this->where, $defaults['where'])) { + throw new \API_Exception('Cannot delete ' . $this->getEntityName() . ' with no "where" parameter specified'); + } + + $items = $this->getObjects(); + + if (!$items) { + throw new \API_Exception('Cannot delete ' . $this->getEntityName() . ', no records found with ' . $this->whereClauseToString()); + } + + $ids = $this->deleteObjects($items); + + $result->exchangeArray($ids); + } + + /** + * @param $items + * @return array + * @throws \API_Exception + */ + protected function deleteObjects($items) { + $ids = []; + $baoName = $this->getBaoName(); + + if ($this->getCheckPermissions()) { + foreach ($items as $item) { + $this->checkContactPermissions($baoName, $item); + } + } + + if ($this->getEntityName() !== 'EntityTag' && method_exists($baoName, 'del')) { + foreach ($items as $item) { + $args = [$item['id']]; + $bao = call_user_func_array([$baoName, 'del'], $args); + if ($bao !== FALSE) { + $ids[] = ['id' => $item['id']]; + } + else { + throw new \API_Exception("Could not delete {$this->getEntityName()} id {$item['id']}"); + } + } + } + else { + foreach ($items as $item) { + $bao = new $baoName(); + $bao->id = $item['id']; + // delete it + $action_result = $bao->delete(); + if ($action_result) { + $ids[] = ['id' => $item['id']]; + } + else { + throw new \API_Exception("Could not delete {$this->getEntityName()} id {$item['id']}"); + } + } + } + return $ids; + } + +} diff --git a/Civi/Api4/Generic/DAOEntity.php b/Civi/Api4/Generic/DAOEntity.php new file mode 100644 index 000000000000..9842cd570372 --- /dev/null +++ b/Civi/Api4/Generic/DAOEntity.php @@ -0,0 +1,94 @@ +setDefaultWhereClause(); + $result->exchangeArray($this->getObjects()); + } + +} diff --git a/Civi/Api4/Generic/DAOGetFieldsAction.php b/Civi/Api4/Generic/DAOGetFieldsAction.php new file mode 100644 index 000000000000..2565f8f29c35 --- /dev/null +++ b/Civi/Api4/Generic/DAOGetFieldsAction.php @@ -0,0 +1,87 @@ +_itemsToGet('name'); + /** @var \Civi\Api4\Service\Spec\SpecGatherer $gatherer */ + $gatherer = \Civi::container()->get('spec_gatherer'); + // Any fields name with a dot in it is custom + if ($fields) { + $this->includeCustom = strpos(implode('', $fields), '.') !== FALSE; + } + $spec = $gatherer->getSpec($this->getEntityName(), $this->getAction(), $this->includeCustom); + return SpecFormatter::specToArray($spec->getFields($fields), $this->loadOptions); + } + + public function fields() { + $fields = parent::fields(); + $fields[] = [ + 'name' => 'custom_field_id', + 'data_type' => 'Integer', + ]; + $fields[] = [ + 'name' => 'custom_group_id', + 'data_type' => 'Integer', + ]; + return $fields; + } + +} diff --git a/Civi/Api4/Generic/DAOSaveAction.php b/Civi/Api4/Generic/DAOSaveAction.php new file mode 100644 index 000000000000..634cd8e14aa1 --- /dev/null +++ b/Civi/Api4/Generic/DAOSaveAction.php @@ -0,0 +1,67 @@ +records as &$record) { + $record += $this->defaults; + if (empty($record['id'])) { + $this->fillDefaults($record); + } + } + $this->validateValues(); + + $resultArray = $this->writeObjects($this->records); + + $result->exchangeArray($resultArray); + } + +} diff --git a/Civi/Api4/Generic/DAOUpdateAction.php b/Civi/Api4/Generic/DAOUpdateAction.php new file mode 100644 index 000000000000..7c8c0475a6e0 --- /dev/null +++ b/Civi/Api4/Generic/DAOUpdateAction.php @@ -0,0 +1,97 @@ +values['id'])) { + $wheres = array_column($this->where, NULL, 0); + if (!isset($wheres['id'])) { + $this->addWhere('id', '=', $this->values['id']); + } + elseif (!($wheres['id'][1] === '=' && $wheres['id'][2] == $this->values['id'])) { + throw new \Exception("Cannot update the id of an existing " . $this->getEntityName() . '.'); + } + } + + // Require WHERE if we didn't get an ID from values + if (!$this->where) { + throw new \API_Exception('Parameter "where" is required unless an id is supplied in values.'); + } + + // Update a single record by ID unless select requires more than id + if ($this->getSelect() === ['id'] && count($this->where) === 1 && $this->where[0][0] === 'id' && $this->where[0][1] === '=' && !empty($this->where[0][2])) { + $this->values['id'] = $this->where[0][2]; + $result->exchangeArray($this->writeObjects([$this->values])); + return; + } + + // Batch update 1 or more records based on WHERE clause + $items = $this->getObjects(); + foreach ($items as &$item) { + $item = $this->values + $item; + } + + if (!$items) { + throw new \API_Exception('Cannot ' . $this->getActionName() . ' ' . $this->getEntityName() . ', no records found with ' . $this->whereClauseToString()); + } + + $result->exchangeArray($this->writeObjects($items)); + } + +} diff --git a/Civi/Api4/Generic/Result.php b/Civi/Api4/Generic/Result.php new file mode 100644 index 000000000000..9930ce813f74 --- /dev/null +++ b/Civi/Api4/Generic/Result.php @@ -0,0 +1,131 @@ +getArrayCopy(); + return array_pop($items); + } + + /** + * @param int $index + * @return array|null + */ + public function itemAt($index) { + $length = $index < 0 ? 0 - $index : $index + 1; + if ($length > count($this)) { + return NULL; + } + return array_slice(array_values($this->getArrayCopy()), $index, 1)[0]; + } + + /** + * Re-index the results array (which by default is non-associative) + * + * Drops any item from the results that does not contain the specified key + * + * @param string $key + * @return $this + * @throws \API_Exception + */ + public function indexBy($key) { + $this->indexedBy = $key; + if (count($this)) { + $newResults = []; + foreach ($this as $values) { + if (isset($values[$key])) { + $newResults[$values[$key]] = $values; + } + } + if (!$newResults) { + throw new \API_Exception("Key $key not found in api results"); + } + $this->exchangeArray($newResults); + } + return $this; + } + + /** + * Returns the number of results + * + * @return int + */ + public function count() { + $count = parent::count(); + if ($count == 1 && is_array($this->first()) && array_keys($this->first()) == ['row_count']) { + return $this->first()['row_count']; + } + return $count; + } + + /** + * Reduce each result to one field + * + * @param $name + * @return array + */ + public function column($name) { + return array_column($this->getArrayCopy(), $name, $this->indexedBy); + } + +} diff --git a/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php b/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php new file mode 100644 index 000000000000..36ae76227a15 --- /dev/null +++ b/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php @@ -0,0 +1,236 @@ +filterArray($values); + $values = $this->sortArray($values); + $values = $this->limitArray($values); + $values = $this->selectArray($values); + return $values; + } + + /** + * @param array $values + * @return array + */ + protected function filterArray($values) { + if ($this->getWhere()) { + $values = array_filter($values, [$this, 'evaluateFilters']); + } + return array_values($values); + } + + /** + * @param array $row + * @return bool + */ + private function evaluateFilters($row) { + $where = $this->getWhere(); + $allConditions = in_array($where[0], ['AND', 'OR', 'NOT']) ? $where : ['AND', $where]; + return $this->walkFilters($row, $allConditions); + } + + /** + * @param array $row + * @param array $filters + * @return bool + * @throws \Civi\API\Exception\NotImplementedException + */ + private function walkFilters($row, $filters) { + switch ($filters[0]) { + case 'AND': + case 'NOT': + $result = TRUE; + foreach ($filters[1] as $filter) { + if (!$this->walkFilters($row, $filter)) { + $result = FALSE; + break; + } + } + return $result == ($filters[0] == 'AND'); + + case 'OR': + $result = !count($filters[1]); + foreach ($filters[1] as $filter) { + if ($this->walkFilters($row, $filter)) { + return TRUE; + } + } + return $result; + + default: + return $this->filterCompare($row, $filters); + } + } + + /** + * @param array $row + * @param array $condition + * @return bool + * @throws \Civi\API\Exception\NotImplementedException + */ + private function filterCompare($row, $condition) { + if (!is_array($condition)) { + throw new NotImplementedException('Unexpected where syntax; expecting array.'); + } + $value = isset($row[$condition[0]]) ? $row[$condition[0]] : NULL; + $operator = $condition[1]; + $expected = isset($condition[2]) ? $condition[2] : NULL; + switch ($operator) { + case '=': + case '!=': + case '<>': + $equal = $value == $expected; + // PHP is too imprecise about comparing the number 0 + if ($expected === 0 || $expected === '0') { + $equal = ($value === 0 || $value === '0'); + } + // PHP is too imprecise about comparing empty strings + if ($expected === '') { + $equal = ($value === ''); + } + return $equal == ($operator == '='); + + case 'IS NULL': + case 'IS NOT NULL': + return is_null($value) == ($operator == 'IS NULL'); + + case '>': + return $value > $expected; + + case '>=': + return $value >= $expected; + + case '<': + return $value < $expected; + + case '<=': + return $value <= $expected; + + case 'BETWEEN': + case 'NOT BETWEEN': + $between = ($value >= $expected[0] && $value <= $expected[1]); + return $between == ($operator == 'BETWEEN'); + + case 'LIKE': + case 'NOT LIKE': + $pattern = '/^' . str_replace('%', '.*', preg_quote($expected, '/')) . '$/i'; + return !preg_match($pattern, $value) == ($operator != 'LIKE'); + + case 'IN': + return in_array($value, $expected); + + case 'NOT IN': + return !in_array($value, $expected); + + default: + throw new NotImplementedException("Unsupported operator: '$operator' cannot be used with array data"); + } + } + + /** + * @param $values + * @return array + */ + protected function sortArray($values) { + if ($this->getOrderBy()) { + usort($values, [$this, 'sortCompare']); + } + return $values; + } + + private function sortCompare($a, $b) { + foreach ($this->getOrderBy() as $field => $dir) { + $modifier = $dir == 'ASC' ? 1 : -1; + if (isset($a[$field]) && isset($b[$field])) { + if ($a[$field] == $b[$field]) { + continue; + } + return (strnatcasecmp($a[$field], $b[$field]) * $modifier); + } + elseif (isset($a[$field]) || isset($b[$field])) { + return ((isset($a[$field]) ? 1 : -1) * $modifier); + } + } + return 0; + } + + /** + * @param $values + * @return array + */ + protected function selectArray($values) { + if ($this->getSelect() === ['row_count']) { + $values = [['row_count' => count($values)]]; + } + elseif ($this->getSelect()) { + foreach ($values as &$value) { + $value = array_intersect_key($value, array_flip($this->getSelect())); + } + } + return $values; + } + + /** + * @param $values + * @return array + */ + protected function limitArray($values) { + if ($this->getOffset() || $this->getLimit()) { + $values = array_slice($values, $this->getOffset() ?: 0, $this->getLimit() ?: NULL); + } + return $values; + } + +} diff --git a/Civi/Api4/Generic/Traits/CustomValueActionTrait.php b/Civi/Api4/Generic/Traits/CustomValueActionTrait.php new file mode 100644 index 000000000000..ba064a4f56ae --- /dev/null +++ b/Civi/Api4/Generic/Traits/CustomValueActionTrait.php @@ -0,0 +1,127 @@ +customGroup = $customGroup; + parent::__construct('CustomValue', $actionName, ['id', 'entity_id']); + } + + /** + * Custom Group name if this is a CustomValue pseudo-entity. + * + * @var string + */ + private $customGroup; + + /** + * @inheritDoc + */ + public function getEntityName() { + return 'Custom_' . $this->getCustomGroup(); + } + + /** + * @inheritDoc + */ + protected function writeObjects($items) { + $result = []; + $fields = $this->entityFields(); + foreach ($items as $item) { + FormattingUtil::formatWriteParams($item, $this->getEntityName(), $fields); + + // Convert field names to custom_xx format + foreach ($fields as $name => $field) { + if (!empty($field['custom_field_id']) && isset($item[$name])) { + $item['custom_' . $field['custom_field_id']] = $item[$name]; + unset($item[$name]); + } + } + + $result[] = \CRM_Core_BAO_CustomValueTable::setValues($item); + } + return $result; + } + + /** + * @inheritDoc + */ + protected function deleteObjects($items) { + $customTable = CoreUtil::getCustomTableByName($this->getCustomGroup()); + $ids = []; + foreach ($items as $item) { + \CRM_Utils_Hook::pre('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray); + \CRM_Utils_SQL_Delete::from($customTable) + ->where('id = #value') + ->param('#value', $item['id']) + ->execute(); + \CRM_Utils_Hook::post('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray); + $ids[] = $item['id']; + } + return $ids; + } + + /** + * @inheritDoc + */ + protected function fillDefaults(&$params) { + foreach ($this->entityFields() as $name => $field) { + if (!isset($params[$name]) && isset($field['default_value'])) { + $params[$name] = $field['default_value']; + } + } + } + + /** + * @return string + */ + public function getCustomGroup() { + return $this->customGroup; + } + +} diff --git a/Civi/Api4/Generic/Traits/DAOActionTrait.php b/Civi/Api4/Generic/Traits/DAOActionTrait.php new file mode 100644 index 000000000000..29e30f86ea93 --- /dev/null +++ b/Civi/Api4/Generic/Traits/DAOActionTrait.php @@ -0,0 +1,290 @@ +getEntityName()); + } + + /** + * Extract the true fields from a BAO + * + * (Used by create and update actions) + * @param object $bao + * @return array + */ + public static function baoToArray($bao) { + $fields = $bao->fields(); + $values = []; + foreach ($fields as $key => $field) { + $name = $field['name']; + if (property_exists($bao, $name)) { + $values[$name] = isset($bao->$name) ? $bao->$name : NULL; + } + } + return $values; + } + + /** + * @return array|int + */ + protected function getObjects() { + $query = new Api4SelectQuery($this->getEntityName(), $this->getCheckPermissions(), $this->entityFields()); + $query->select = $this->getSelect(); + $query->where = $this->getWhere(); + $query->orderBy = $this->getOrderBy(); + $query->limit = $this->getLimit(); + $query->offset = $this->getOffset(); + return $query->run(); + } + + /** + * Fill field defaults which were declared by the api. + * + * Note: default values from core are ignored because the BAO or database layer will supply them. + * + * @param array $params + */ + protected function fillDefaults(&$params) { + $fields = $this->entityFields(); + $bao = $this->getBaoName(); + $coreFields = array_column($bao::fields(), NULL, 'name'); + + foreach ($fields as $name => $field) { + // If a default value in the api field is different than in core, the api should override it. + if (!isset($params[$name]) && !empty($field['default_value']) && $field['default_value'] != \CRM_Utils_Array::pathGet($coreFields, [$name, 'default'])) { + $params[$name] = $field['default_value']; + } + } + } + + /** + * Write bao objects as part of a create/update action. + * + * @param array $items + * The records to write to the DB. + * @return array + * The records after being written to the DB (e.g. including newly assigned "id"). + * @throws \API_Exception + */ + protected function writeObjects($items) { + $baoName = $this->getBaoName(); + + // Some BAOs are weird and don't support a straightforward "create" method. + $oddballs = [ + 'EntityTag' => 'add', + 'GroupContact' => 'add', + 'Website' => 'add', + ]; + $method = $oddballs[$this->getEntityName()] ?? 'create'; + if (!method_exists($baoName, $method)) { + $method = 'add'; + } + + $result = []; + + foreach ($items as $item) { + $entityId = UtilsArray::value('id', $item); + FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->entityFields()); + $this->formatCustomParams($item, $entityId); + $item['check_permissions'] = $this->getCheckPermissions(); + + // For some reason the contact bao requires this + if ($entityId && $this->getEntityName() == 'Contact') { + $item['contact_id'] = $entityId; + } + + if ($this->getCheckPermissions()) { + $this->checkContactPermissions($baoName, $item); + } + + if ($this->getEntityName() == 'Address') { + $createResult = $baoName::add($item, $this->fixAddress); + } + elseif (method_exists($baoName, $method)) { + $createResult = $baoName::$method($item); + } + else { + $createResult = $this->genericCreateMethod($item); + } + + if (!$createResult) { + $errMessage = sprintf('%s write operation failed', $this->getEntityName()); + throw new \API_Exception($errMessage); + } + + if (!empty($this->reload) && is_a($createResult, 'CRM_Core_DAO')) { + $createResult->find(TRUE); + } + + // trim back the junk and just get the array: + $resultArray = $this->baoToArray($createResult); + + $result[] = $resultArray; + } + return $result; + } + + /** + * Fallback when a BAO does not contain create or add functions + * + * @param $params + * @return mixed + */ + private function genericCreateMethod($params) { + $baoName = $this->getBaoName(); + $hook = empty($params['id']) ? 'create' : 'edit'; + + \CRM_Utils_Hook::pre($hook, $this->getEntityName(), UtilsArray::value('id', $params), $params); + /** @var \CRM_Core_DAO $instance */ + $instance = new $baoName(); + $instance->copyValues($params, TRUE); + $instance->save(); + \CRM_Utils_Hook::post($hook, $this->getEntityName(), $instance->id, $instance); + + return $instance; + } + + /** + * @param array $params + * @param int $entityId + * @return mixed + */ + protected function formatCustomParams(&$params, $entityId) { + $customParams = []; + + // $customValueID is the ID of the custom value in the custom table for this + // entity (i guess this assumes it's not a multi value entity) + foreach ($params as $name => $value) { + if (strpos($name, '.') === FALSE) { + continue; + } + + list($customGroup, $customField) = explode('.', $name); + + $customFieldId = \CRM_Core_BAO_CustomField::getFieldValue( + \CRM_Core_DAO_CustomField::class, + $customField, + 'id', + 'name' + ); + $customFieldType = \CRM_Core_BAO_CustomField::getFieldValue( + \CRM_Core_DAO_CustomField::class, + $customField, + 'html_type', + 'name' + ); + $customFieldExtends = \CRM_Core_BAO_CustomGroup::getFieldValue( + \CRM_Core_DAO_CustomGroup::class, + $customGroup, + 'extends', + 'name' + ); + + // todo are we sure we don't want to allow setting to NULL? need to test + if ($customFieldId && NULL !== $value) { + + if ($customFieldType == 'CheckBox') { + // this function should be part of a class + formatCheckBoxField($value, 'custom_' . $customFieldId, $this->getEntityName()); + } + + \CRM_Core_BAO_CustomField::formatCustomField( + $customFieldId, + $customParams, + $value, + $customFieldExtends, + // todo check when this is needed + NULL, + $entityId, + FALSE, + FALSE, + TRUE + ); + } + } + + if ($customParams) { + $params['custom'] = $customParams; + } + } + + /** + * Check edit/delete permissions for contacts and related entities. + * + * @param $baoName + * @param $item + * @throws \Civi\API\Exception\UnauthorizedException + */ + protected function checkContactPermissions($baoName, $item) { + if ($baoName == 'CRM_Contact_BAO_Contact' && !empty($item['id'])) { + $permission = $this->getActionName() == 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT; + if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) { + throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record'); + } + } + else { + // Fixme: decouple from v3 + require_once 'api/v3/utils.php'; + _civicrm_api3_check_edit_permissions($baoName, ['check_permissions' => 1] + $item); + } + } + +} diff --git a/Civi/Api4/Generic/Traits/IsCurrentTrait.php b/Civi/Api4/Generic/Traits/IsCurrentTrait.php new file mode 100644 index 000000000000..edd02d8e5ec3 --- /dev/null +++ b/Civi/Api4/Generic/Traits/IsCurrentTrait.php @@ -0,0 +1,75 @@ += now) AND (start_date IS NULL OR start_DATE <= now) + * + * Adding current = FALSE is a shortcut for + * WHERE is_active = 0 OR start_date > now OR end_date < now + * + * @var bool + */ + protected $current; + + /** + * @return bool + */ + public function getCurrent() { + return $this->current; + } + + /** + * @param bool $current + * @return $this + */ + public function setCurrent($current) { + $this->current = $current; + return $this; + } + +} diff --git a/Civi/Api4/Group.php b/Civi/Api4/Group.php new file mode 100644 index 000000000000..b21754603be2 --- /dev/null +++ b/Civi/Api4/Group.php @@ -0,0 +1,47 @@ + [ + ['onApiResolve', Events::W_EARLY], + ], + ]; + } + + /** + * @param \Civi\API\Event\ResolveEvent $event + * API resolution event. + */ + public function onApiResolve(ResolveEvent $event) { + $apiRequest = $event->getApiRequest(); + if ($apiRequest instanceof AbstractAction) { + $event->setApiRequest($apiRequest); + $event->setApiProvider($this); + $event->stopPropagation(); + } + } + + /** + * @inheritDoc + * + * @param \Civi\Api4\Generic\AbstractAction $action + * + * @return \Civi\Api4\Generic\Result + */ + public function invoke($action) { + // Load result class based on @return annotation in the execute() method. + $reflection = new \ReflectionClass($action); + $doc = ReflectionUtils::getCodeDocs($reflection->getMethod('execute'), 'Method'); + $resultClass = \CRM_Utils_Array::value('return', $doc, '\\Civi\\Api4\\Generic\\Result'); + $result = new $resultClass(); + $result->action = $action->getActionName(); + $result->entity = $action->getEntityName(); + $action->_run($result); + $this->handleChains($action, $result); + return $result; + } + + /** + * Run each chained action once per row + * + * @param \Civi\Api4\Generic\AbstractAction $action + * @param \Civi\Api4\Generic\Result $result + */ + protected function handleChains($action, $result) { + foreach ($action->getChain() as $name => $request) { + $request += [NULL, NULL, [], NULL]; + $request[2]['checkPermissions'] = $action->getCheckPermissions(); + foreach ($result as &$row) { + $row[$name] = $this->runChain($request, $row); + } + } + } + + /** + * Run a chained action + * + * @param $request + * @param $row + * @return array|\Civi\Api4\Generic\Result|null + * @throws \API_Exception + */ + protected function runChain($request, $row) { + list($entity, $action, $params, $index) = $request; + // Swap out variables in $entity, $action & $params + $this->resolveChainLinks($entity, $row); + $this->resolveChainLinks($action, $row); + $this->resolveChainLinks($params, $row); + return (array) civicrm_api4($entity, $action, $params, $index); + } + + /** + * Swap out variable names + * + * @param mixed $val + * @param array $result + */ + protected function resolveChainLinks(&$val, $result) { + if (is_array($val)) { + foreach ($val as &$v) { + $this->resolveChainLinks($v, $result); + } + } + elseif (is_string($val) && strlen($val) > 1 && substr($val, 0, 1) === '$') { + $val = \CRM_Utils_Array::pathGet($result, explode('.', substr($val, 1))); + } + } + + /** + * @inheritDoc + * @param int $version + * @return array + */ + public function getEntityNames($version) { + /** FIXME */ + return []; + } + + /** + * @inheritDoc + * @param int $version + * @param string $entity + * @return array + */ + public function getActionNames($version, $entity) { + /** FIXME Civi\API\V4\Action\GetActions */ + return []; + } + +} diff --git a/Civi/Api4/Query/Api4SelectQuery.php b/Civi/Api4/Query/Api4SelectQuery.php new file mode 100644 index 000000000000..f7026ba34943 --- /dev/null +++ b/Civi/Api4/Query/Api4SelectQuery.php @@ -0,0 +1,580 @@ +=', '>', '<', 'LIKE', "<>", "!=", + * * "NOT LIKE", 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', + * * 'IS NOT NULL', or 'IS NULL'. + */ +class Api4SelectQuery extends SelectQuery { + + /** + * @var int + */ + protected $apiVersion = 4; + + /** + * @var array + * Maps select fields to [, ] + */ + protected $fkSelectAliases = []; + + /** + * @var \Civi\Api4\Service\Schema\Joinable\Joinable[] + * The joinable tables that have been joined so far + */ + protected $joinedTables = []; + + /** + * @param string $entity + * @param bool $checkPermissions + * @param array $fields + */ + public function __construct($entity, $checkPermissions, $fields) { + require_once 'api/v3/utils.php'; + $this->entity = $entity; + $this->checkPermissions = $checkPermissions; + + $baoName = CoreUtil::getBAOFromApiName($entity); + $bao = new $baoName(); + + $this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($bao)); + $this->apiFieldSpec = (array) $fields; + + \CRM_Utils_SQL_Select::from($this->getTableName($baoName) . ' ' . self::MAIN_TABLE_ALIAS); + + // Add ACLs first to avoid redundant subclauses + $this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName)); + } + + /** + * Why walk when you can + * + * @return array|int + */ + public function run() { + $this->addJoins(); + $this->buildSelectFields(); + $this->buildWhereClause(); + + // Select + if (in_array('row_count', $this->select)) { + $this->query->select("count(*) as c"); + } + else { + foreach ($this->selectFields as $column => $alias) { + $this->query->select("$column as `$alias`"); + } + // Order by + $this->buildOrderBy(); + } + + // Limit + if (!empty($this->limit) || !empty($this->offset)) { + $this->query->limit($this->limit, $this->offset); + } + + $results = []; + $sql = $this->query->toSQL(); + $query = \CRM_Core_DAO::executeQuery($sql); + + while ($query->fetch()) { + if (in_array('row_count', $this->select)) { + $results[]['row_count'] = (int) $query->c; + break; + } + $results[$query->id] = []; + foreach ($this->selectFields as $column => $alias) { + $returnName = $alias; + $alias = str_replace('.', '_', $alias); + $results[$query->id][$returnName] = property_exists($query, $alias) ? $query->$alias : NULL; + }; + } + $event = new PostSelectQueryEvent($results, $this); + \Civi::dispatcher()->dispatch(Events::POST_SELECT_QUERY, $event); + + return $event->getResults(); + } + + /** + * Gets all FK fields and does the required joins + */ + protected function addJoins() { + $allFields = array_merge($this->select, array_keys($this->orderBy)); + $recurse = function($clauses) use (&$allFields, &$recurse) { + foreach ($clauses as $clause) { + if ($clause[0] === 'NOT' && is_string($clause[1][0])) { + $recurse($clause[1][1]); + } + elseif (in_array($clause[0], ['AND', 'OR', 'NOT'])) { + $recurse($clause[1]); + } + elseif (is_array($clause[0])) { + array_walk($clause, $recurse); + } + else { + $allFields[] = $clause[0]; + } + } + }; + $recurse($this->where); + $dotFields = array_unique(array_filter($allFields, function ($field) { + return strpos($field, '.') !== FALSE; + })); + + foreach ($dotFields as $dotField) { + $this->joinFK($dotField); + } + } + + /** + * Populate $this->selectFields + * + * @throws \Civi\API\Exception\UnauthorizedException + */ + protected function buildSelectFields() { + $return_all_fields = (empty($this->select) || !is_array($this->select)); + $return = $return_all_fields ? $this->entityFieldNames : $this->select; + if ($return_all_fields || in_array('custom', $this->select)) { + foreach (array_keys($this->apiFieldSpec) as $fieldName) { + if (strpos($fieldName, 'custom_') === 0) { + $return[] = $fieldName; + } + } + } + + // Always select the ID if the table has one. + if (array_key_exists('id', $this->apiFieldSpec) || strstr($this->entity, 'Custom_')) { + $this->selectFields[self::MAIN_TABLE_ALIAS . ".id"] = "id"; + } + + // core return fields + foreach ($return as $fieldName) { + $field = $this->getField($fieldName); + if (strpos($fieldName, '.') && !empty($this->fkSelectAliases[$fieldName]) && !array_filter($this->getPathJoinTypes($fieldName))) { + $this->selectFields[$this->fkSelectAliases[$fieldName]] = $fieldName; + } + elseif ($field && in_array($field['name'], $this->entityFieldNames)) { + $this->selectFields[self::MAIN_TABLE_ALIAS . "." . UtilsArray::value('column_name', $field, $field['name'])] = $field['name']; + } + } + } + + /** + * @inheritDoc + */ + protected function buildWhereClause() { + foreach ($this->where as $clause) { + $sql_clause = $this->treeWalkWhereClause($clause); + $this->query->where($sql_clause); + } + } + + /** + * @inheritDoc + */ + protected function buildOrderBy() { + foreach ($this->orderBy as $field => $dir) { + if ($dir !== 'ASC' && $dir !== 'DESC') { + throw new \API_Exception("Invalid sort direction. Cannot order by $field $dir"); + } + if ($this->getField($field)) { + $this->query->orderBy(self::MAIN_TABLE_ALIAS . '.' . $field . " $dir"); + } + else { + throw new \API_Exception("Invalid sort field. Cannot order by $field $dir"); + } + } + } + + /** + * Recursively validate and transform a branch or leaf clause array to SQL. + * + * @param array $clause + * @return string SQL where clause + * + * @uses validateClauseAndComposeSql() to generate the SQL etc. + * @todo if an 'and' is nested within and 'and' (or or-in-or) then should + * flatten that to be a single list of clauses. + */ + protected function treeWalkWhereClause($clause) { + switch ($clause[0]) { + case 'OR': + case 'AND': + // handle branches + if (count($clause[1]) === 1) { + // a single set so AND|OR is immaterial + return $this->treeWalkWhereClause($clause[1][0]); + } + else { + $sql_subclauses = []; + foreach ($clause[1] as $subclause) { + $sql_subclauses[] = $this->treeWalkWhereClause($subclause); + } + return '(' . implode("\n" . $clause[0], $sql_subclauses) . ')'; + } + + case 'NOT': + // If we get a group of clauses with no operator, assume AND + if (!is_string($clause[1][0])) { + $clause[1] = ['AND', $clause[1]]; + } + return 'NOT (' . $this->treeWalkWhereClause($clause[1]) . ')'; + + default: + return $this->validateClauseAndComposeSql($clause); + } + } + + /** + * Validate and transform a leaf clause array to SQL. + * @param array $clause [$fieldName, $operator, $criteria] + * @return string SQL + * @throws \API_Exception + * @throws \Exception + */ + protected function validateClauseAndComposeSql($clause) { + // Pad array for unary operators + list($key, $operator, $value) = array_pad($clause, 3, NULL); + $fieldSpec = $this->getField($key); + // derive table and column: + $table_name = NULL; + $column_name = NULL; + if (in_array($key, $this->entityFieldNames)) { + $table_name = self::MAIN_TABLE_ALIAS; + $column_name = $key; + } + elseif (strpos($key, '.') && isset($this->fkSelectAliases[$key])) { + list($table_name, $column_name) = explode('.', $this->fkSelectAliases[$key]); + } + + if (!$table_name || !$column_name) { + throw new \API_Exception("Invalid field '$key' in where clause."); + } + + FormattingUtil::formatValue($value, $fieldSpec, $this->getEntity()); + + $sql_clause = \CRM_Core_DAO::createSQLFilter("`$table_name`.`$column_name`", [$operator => $value]); + if ($sql_clause === NULL) { + throw new \API_Exception("Invalid value in where clause for field '$key'"); + } + return $sql_clause; + } + + /** + * @inheritDoc + */ + protected function getFields() { + return $this->apiFieldSpec; + } + + /** + * Fetch a field from the getFields list + * + * @param string $fieldName + * + * @return string|null + */ + protected function getField($fieldName) { + if ($fieldName) { + $fieldPath = explode('.', $fieldName); + if (count($fieldPath) > 1) { + $fieldName = implode('.', array_slice($fieldPath, -2)); + } + return UtilsArray::value($fieldName, $this->apiFieldSpec); + } + return NULL; + } + + /** + * @param $key + * @throws \API_Exception + */ + protected function joinFK($key) { + $pathArray = explode('.', $key); + + if (count($pathArray) < 2) { + return; + } + + /** @var \Civi\Api4\Service\Schema\Joiner $joiner */ + $joiner = \Civi::container()->get('joiner'); + $field = array_pop($pathArray); + $pathString = implode('.', $pathArray); + + if (!$joiner->canJoin($this, $pathString)) { + return; + } + + $joinPath = $joiner->join($this, $pathString); + /** @var \Civi\Api4\Service\Schema\Joinable\Joinable $lastLink */ + $lastLink = array_pop($joinPath); + + // Cache field info for retrieval by $this->getField() + $prefix = array_pop($pathArray) . '.'; + if (!isset($this->apiFieldSpec[$prefix . $field])) { + $joinEntity = $lastLink->getEntity(); + // Custom fields are already prefixed + if ($lastLink instanceof CustomGroupJoinable) { + $prefix = ''; + } + foreach ($lastLink->getEntityFields() as $fieldObject) { + $this->apiFieldSpec[$prefix . $fieldObject->getName()] = $fieldObject->toArray() + ['entity' => $joinEntity]; + } + } + + if (!$lastLink->getField($field)) { + throw new \API_Exception('Invalid join'); + } + + // custom groups use aliases for field names + if ($lastLink instanceof CustomGroupJoinable) { + $field = $lastLink->getSqlColumn($field); + } + + $this->fkSelectAliases[$key] = sprintf('%s.%s', $lastLink->getAlias(), $field); + } + + /** + * @param \Civi\Api4\Service\Schema\Joinable\Joinable $joinable + * + * @return $this + */ + public function addJoinedTable(Joinable $joinable) { + $this->joinedTables[] = $joinable; + + return $this; + } + + /** + * @return FALSE|string + */ + public function getFrom() { + return AllCoreTables::getTableForClass(AllCoreTables::getFullName($this->entity)); + } + + /** + * @return string + */ + public function getEntity() { + return $this->entity; + } + + /** + * @return array + */ + public function getSelect() { + return $this->select; + } + + /** + * @return array + */ + public function getWhere() { + return $this->where; + } + + /** + * @return array + */ + public function getOrderBy() { + return $this->orderBy; + } + + /** + * @return mixed + */ + public function getLimit() { + return $this->limit; + } + + /** + * @return mixed + */ + public function getOffset() { + return $this->offset; + } + + /** + * @return array + */ + public function getSelectFields() { + return $this->selectFields; + } + + /** + * @return bool + */ + public function isFillUniqueFields() { + return $this->isFillUniqueFields; + } + + /** + * @return \CRM_Utils_SQL_Select + */ + public function getQuery() { + return $this->query; + } + + /** + * @return array + */ + public function getJoins() { + return $this->joins; + } + + /** + * @return array + */ + public function getApiFieldSpec() { + return $this->apiFieldSpec; + } + + /** + * @return array + */ + public function getEntityFieldNames() { + return $this->entityFieldNames; + } + + /** + * @return array + */ + public function getAclFields() { + return $this->aclFields; + } + + /** + * @return bool|string + */ + public function getCheckPermissions() { + return $this->checkPermissions; + } + + /** + * @return int + */ + public function getApiVersion() { + return $this->apiVersion; + } + + /** + * @return array + */ + public function getFkSelectAliases() { + return $this->fkSelectAliases; + } + + /** + * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] + */ + public function getJoinedTables() { + return $this->joinedTables; + } + + /** + * @return \Civi\Api4\Service\Schema\Joinable\Joinable + */ + public function getJoinedTable($alias) { + foreach ($this->joinedTables as $join) { + if ($join->getAlias() == $alias) { + return $join; + } + } + } + + /** + * Get table name on basis of entity + * + * @param string $baoName + * + * @return void + */ + public function getTableName($baoName) { + if (strstr($this->entity, 'Custom_')) { + $this->query = \CRM_Utils_SQL_Select::from(CoreUtil::getCustomTableByName(str_replace('Custom_', '', $this->entity)) . ' ' . self::MAIN_TABLE_ALIAS); + $this->entityFieldNames = array_keys($this->apiFieldSpec); + } + else { + $bao = new $baoName(); + $this->query = \CRM_Utils_SQL_Select::from($bao->tableName() . ' ' . self::MAIN_TABLE_ALIAS); + } + } + + /** + * Separates a string like 'emails.location_type.label' into an array, where + * each value in the array tells whether it is 1-1 or 1-n join type + * + * @param string $pathString + * Dot separated path to the field + * + * @return array + * Index is table alias and value is boolean whether is 1-to-many join + */ + public function getPathJoinTypes($pathString) { + $pathParts = explode('.', $pathString); + // remove field + array_pop($pathParts); + $path = []; + $query = $this; + $isMultipleChecker = function($alias) use ($query) { + foreach ($query->getJoinedTables() as $table) { + if ($table->getAlias() === $alias) { + return $table->getJoinType() === Joinable::JOIN_TYPE_ONE_TO_MANY; + } + } + return FALSE; + }; + + foreach ($pathParts as $part) { + $path[$part] = $isMultipleChecker($part); + } + + return $path; + } + +} diff --git a/Civi/Api4/Relationship.php b/Civi/Api4/Relationship.php new file mode 100644 index 000000000000..888e98d9f088 --- /dev/null +++ b/Civi/Api4/Relationship.php @@ -0,0 +1,54 @@ + $item) { + $result[] = ['path' => $path] + $item; + } + return $result; + }); + } + + public static function getFields() { + return new BasicGetFieldsAction(__CLASS__, __FUNCTION__, function() { + return [ + [ + 'name' => 'path', + 'title' => 'Relative Path', + 'required' => TRUE, + 'data_type' => 'String', + ], + [ + 'name' => 'title', + 'title' => 'Page Title', + 'required' => TRUE, + 'data_type' => 'String', + ], + [ + 'name' => 'page_callback', + 'title' => 'Page Callback', + 'required' => TRUE, + 'data_type' => 'String', + ], + [ + 'name' => 'page_arguments', + 'title' => 'Page Arguments', + 'required' => FALSE, + 'data_type' => 'String', + ], + [ + 'name' => 'path_arguments', + 'title' => 'Path Arguments', + 'required' => FALSE, + 'data_type' => 'String', + ], + [ + 'name' => 'access_arguments', + 'title' => 'Access Arguments', + 'required' => FALSE, + 'data_type' => 'Array', + ], + ]; + }); + } + + /** + * @return array + */ + public static function permissions() { + return [ + "meta" => ["access CiviCRM"], + "default" => ["administer CiviCRM"], + ]; + } + +} diff --git a/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php b/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php new file mode 100644 index 000000000000..18fa632332b1 --- /dev/null +++ b/Civi/Api4/Service/Schema/Joinable/ActivityToActivityContactAssigneesJoinable.php @@ -0,0 +1,75 @@ +addCondition(sprintf('%s.record_type_id = (%s)', $alias, $subSelect)); + parent::__construct('civicrm_activity_contact', 'activity_id', $alias); + } + +} diff --git a/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php b/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php new file mode 100644 index 000000000000..f294be8b3f54 --- /dev/null +++ b/Civi/Api4/Service/Schema/Joinable/BridgeJoinable.php @@ -0,0 +1,58 @@ +middleLink = $middleLink; + } + + /** + * @return Joinable + */ + public function getMiddleLink() { + return $this->middleLink; + } + +} diff --git a/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php b/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php new file mode 100644 index 000000000000..c74b861bf826 --- /dev/null +++ b/Civi/Api4/Service/Schema/Joinable/CustomGroupJoinable.php @@ -0,0 +1,108 @@ +entity = $entity; + $this->columns = $columns; + parent::__construct($targetTable, 'entity_id', $alias); + $this->joinType = $isMultiRecord ? + self::JOIN_TYPE_ONE_TO_MANY : self::JOIN_TYPE_ONE_TO_ONE; + } + + /** + * @inheritDoc + */ + public function getEntityFields() { + if (!$this->entityFields) { + $fields = CustomField::get() + ->setCheckPermissions(FALSE) + ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_required', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value', 'date_format', 'time_format', 'start_date_years', 'end_date_years']) + ->addWhere('custom_group.table_name', '=', $this->getTargetTable()) + ->execute(); + foreach ($fields as $field) { + $this->entityFields[] = \Civi\Api4\Service\Spec\SpecFormatter::arrayToField($field, $this->getEntity()); + } + } + return $this->entityFields; + } + + /** + * @inheritDoc + */ + public function getField($fieldName) { + foreach ($this->getEntityFields() as $field) { + $name = $field->getName(); + if ($name === $fieldName || strrpos($name, '.' . $fieldName) === strlen($name) - strlen($fieldName) - 1) { + return $field; + } + } + return NULL; + } + + /** + * @return string + */ + public function getSqlColumn($fieldName) { + return $this->columns[$fieldName]; + } + +} diff --git a/Civi/Api4/Service/Schema/Joinable/Joinable.php b/Civi/Api4/Service/Schema/Joinable/Joinable.php new file mode 100644 index 000000000000..18478c8b2412 --- /dev/null +++ b/Civi/Api4/Service/Schema/Joinable/Joinable.php @@ -0,0 +1,312 @@ +targetTable = $targetTable; + $this->targetColumn = $targetColumn; + if (!$this->entity) { + $this->entity = CoreUtil::getApiNameFromTableName($targetTable); + } + $this->alias = $alias ?: str_replace('civicrm_', '', $targetTable); + } + + /** + * Gets conditions required when joining to a base table + * + * @param string|null $baseTableAlias + * Name of the base table, if aliased. + * + * @return array + */ + public function getConditionsForJoin($baseTableAlias = NULL) { + $baseCondition = sprintf( + '%s.%s = %s.%s', + $baseTableAlias ?: $this->baseTable, + $this->baseColumn, + $this->getAlias(), + $this->targetColumn + ); + + return array_merge([$baseCondition], $this->conditions); + } + + /** + * @return string + */ + public function getBaseTable() { + return $this->baseTable; + } + + /** + * @param string $baseTable + * + * @return $this + */ + public function setBaseTable($baseTable) { + $this->baseTable = $baseTable; + + return $this; + } + + /** + * @return string + */ + public function getBaseColumn() { + return $this->baseColumn; + } + + /** + * @param string $baseColumn + * + * @return $this + */ + public function setBaseColumn($baseColumn) { + $this->baseColumn = $baseColumn; + + return $this; + } + + /** + * @return string + */ + public function getAlias() { + return $this->alias; + } + + /** + * @param string $alias + * + * @return $this + */ + public function setAlias($alias) { + $this->alias = $alias; + + return $this; + } + + /** + * @return string + */ + public function getTargetTable() { + return $this->targetTable; + } + + /** + * @return string + */ + public function getTargetColumn() { + return $this->targetColumn; + } + + /** + * @return string + */ + public function getEntity() { + return $this->entity; + } + + /** + * @param $condition + * + * @return $this + */ + public function addCondition($condition) { + $this->conditions[] = $condition; + + return $this; + } + + /** + * @return array + */ + public function getExtraJoinConditions() { + return $this->conditions; + } + + /** + * @param array $conditions + * + * @return $this + */ + public function setConditions($conditions) { + $this->conditions = $conditions; + + return $this; + } + + /** + * @return string + */ + public function getJoinSide() { + return $this->joinSide; + } + + /** + * @param string $joinSide + * + * @return $this + */ + public function setJoinSide($joinSide) { + $this->joinSide = $joinSide; + + return $this; + } + + /** + * @return int + */ + public function getJoinType() { + return $this->joinType; + } + + /** + * @param int $joinType + * + * @return $this + */ + public function setJoinType($joinType) { + $this->joinType = $joinType; + + return $this; + } + + /** + * @return array + */ + public function toArray() { + return get_object_vars($this); + } + + /** + * @return \Civi\Api4\Service\Spec\FieldSpec[] + */ + public function getEntityFields() { + if (!$this->entityFields) { + $bao = AllCoreTables::getClassForTable($this->getTargetTable()); + if ($bao) { + foreach ($bao::fields() as $field) { + $this->entityFields[] = \Civi\Api4\Service\Spec\SpecFormatter::arrayToField($field, $this->getEntity()); + } + } + } + return $this->entityFields; + } + + /** + * @return \Civi\Api4\Service\Spec\FieldSpec|NULL + */ + public function getField($fieldName) { + foreach ($this->getEntityFields() as $field) { + if ($field->getName() === $fieldName) { + return $field; + } + } + return NULL; + } + +} diff --git a/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php b/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php new file mode 100644 index 000000000000..b5ba1f052778 --- /dev/null +++ b/Civi/Api4/Service/Schema/Joinable/OptionValueJoinable.php @@ -0,0 +1,96 @@ +optionGroupName = $optionGroup; + $optionValueTable = 'civicrm_option_value'; + + // default join alias to option group name, e.g. activity_type + if (!$alias && !is_numeric($optionGroup)) { + $alias = $optionGroup; + } + + parent::__construct($optionValueTable, $keyColumn, $alias); + + if (!is_numeric($optionGroup)) { + $subSelect = 'SELECT id FROM civicrm_option_group WHERE name = "%s"'; + $subQuery = sprintf($subSelect, $optionGroup); + $condition = sprintf('%s.option_group_id = (%s)', $alias, $subQuery); + } + else { + $condition = sprintf('%s.option_group_id = %d', $alias, $optionGroup); + } + + $this->addCondition($condition); + } + + /** + * The existing condition must also be re-aliased + * + * @param string $alias + * + * @return $this + */ + public function setAlias($alias) { + foreach ($this->conditions as $index => $condition) { + $search = $this->alias . '.'; + $replace = $alias . '.'; + $this->conditions[$index] = str_replace($search, $replace, $condition); + } + + parent::setAlias($alias); + + return $this; + } + +} diff --git a/Civi/Api4/Service/Schema/Joiner.php b/Civi/Api4/Service/Schema/Joiner.php new file mode 100644 index 000000000000..99f3b5b0a28c --- /dev/null +++ b/Civi/Api4/Service/Schema/Joiner.php @@ -0,0 +1,132 @@ +schemaMap = $schemaMap; + } + + /** + * @param \Civi\Api4\Query\Api4SelectQuery $query + * The query object to do the joins on + * @param string $joinPath + * A path of aliases in dot notation, e.g. contact.phone + * @param string $side + * Can be LEFT or INNER + * + * @throws \Exception + * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] + * The path used to make the join + */ + public function join(Api4SelectQuery $query, $joinPath, $side = 'LEFT') { + $fullPath = $this->getPath($query->getFrom(), $joinPath); + $baseTable = $query::MAIN_TABLE_ALIAS; + + foreach ($fullPath as $link) { + $target = $link->getTargetTable(); + $alias = $link->getAlias(); + $conditions = $link->getConditionsForJoin($baseTable); + + $query->join($side, $target, $alias, $conditions); + $query->addJoinedTable($link); + + $baseTable = $link->getAlias(); + } + + return $fullPath; + } + + /** + * @param \Civi\Api4\Query\Api4SelectQuery $query + * @param $joinPath + * + * @return bool + */ + public function canJoin(Api4SelectQuery $query, $joinPath) { + return !empty($this->getPath($query->getFrom(), $joinPath)); + } + + /** + * @param string $baseTable + * @param string $joinPath + * + * @return array + * @throws \Exception + */ + protected function getPath($baseTable, $joinPath) { + $cacheKey = sprintf('%s.%s', $baseTable, $joinPath); + if (!isset($this->cache[$cacheKey])) { + $stack = explode('.', $joinPath); + $fullPath = []; + + foreach ($stack as $key => $targetAlias) { + $links = $this->schemaMap->getPath($baseTable, $targetAlias); + + if (empty($links)) { + throw new \Exception(sprintf('Cannot join %s to %s', $baseTable, $targetAlias)); + } + else { + $fullPath = array_merge($fullPath, $links); + $lastLink = end($links); + $baseTable = $lastLink->getTargetTable(); + } + } + + $this->cache[$cacheKey] = $fullPath; + } + + return $this->cache[$cacheKey]; + } + +} diff --git a/Civi/Api4/Service/Schema/SchemaMap.php b/Civi/Api4/Service/Schema/SchemaMap.php new file mode 100644 index 000000000000..0ca16795abc4 --- /dev/null +++ b/Civi/Api4/Service/Schema/SchemaMap.php @@ -0,0 +1,174 @@ +getTableByName($baseTableName); + $path = []; + + if (!$table) { + return $path; + } + + $this->findPaths($table, $targetTableAlias, 1, $path); + + foreach ($path as $index => $pathLink) { + if ($pathLink instanceof BridgeJoinable) { + $start = array_slice($path, 0, $index); + $middle = [$pathLink->getMiddleLink()]; + $end = array_slice($path, $index, count($path) - $index); + $path = array_merge($start, $middle, $end); + } + } + + return $path; + } + + /** + * @return Table[] + */ + public function getTables() { + return $this->tables; + } + + /** + * @param $name + * + * @return Table|null + */ + public function getTableByName($name) { + foreach ($this->tables as $table) { + if ($table->getName() === $name) { + return $table; + } + } + + return NULL; + } + + /** + * Adds a table to the schema map if it has not already been added + * + * @param Table $table + * + * @return $this + */ + public function addTable(Table $table) { + if (!$this->getTableByName($table->getName())) { + $this->tables[] = $table; + } + + return $this; + } + + /** + * @param array $tables + */ + public function addTables(array $tables) { + foreach ($tables as $table) { + $this->addTable($table); + } + } + + /** + * Recursive function to traverse the schema looking for a path + * + * @param Table $table + * The current table to base fromm + * @param string $target + * The target joinable table alias + * @param int $depth + * The current level of recursion which reflects the number of joins needed + * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $path + * (By-reference) The possible paths to the target table + * @param \Civi\Api4\Service\Schema\Joinable\Joinable[] $currentPath + * For internal use only to track the path to reach the target table + */ + private function findPaths(Table $table, $target, $depth, &$path, $currentPath = [] + ) { + static $visited = []; + + // reset if new call + if ($depth === 1) { + $visited = []; + } + + $canBeShorter = empty($path) || count($currentPath) + 1 < count($path); + $tooFar = $depth > self::MAX_JOIN_DEPTH; + $beenHere = in_array($table->getName(), $visited); + + if ($tooFar || $beenHere || !$canBeShorter) { + return; + } + + // prevent circular reference + $visited[] = $table->getName(); + + foreach ($table->getExternalLinks() as $link) { + if ($link->getAlias() === $target) { + $path = array_merge($currentPath, [$link]); + } + else { + $linkTable = $this->getTableByName($link->getTargetTable()); + if ($linkTable) { + $nextStep = array_merge($currentPath, [$link]); + $this->findPaths($linkTable, $target, $depth + 1, $path, $nextStep); + } + } + } + } + +} diff --git a/Civi/Api4/Service/Schema/SchemaMapBuilder.php b/Civi/Api4/Service/Schema/SchemaMapBuilder.php new file mode 100644 index 000000000000..76d11bcc7373 --- /dev/null +++ b/Civi/Api4/Service/Schema/SchemaMapBuilder.php @@ -0,0 +1,252 @@ +dispatcher = $dispatcher; + $this->apiEntities = array_keys((array) Entity::get()->setCheckPermissions(FALSE)->addSelect('name')->execute()->indexBy('name')); + } + + /** + * @return SchemaMap + */ + public function build() { + $map = new SchemaMap(); + $this->loadTables($map); + + $event = new SchemaMapBuildEvent($map); + $this->dispatcher->dispatch(Events::SCHEMA_MAP_BUILD, $event); + + return $map; + } + + /** + * Add all tables and joins + * + * @param SchemaMap $map + */ + private function loadTables(SchemaMap $map) { + /** @var \CRM_Core_DAO $daoName */ + foreach (AllCoreTables::get() as $daoName => $data) { + $table = new Table($data['table']); + foreach ($daoName::fields() as $field => $fieldData) { + $this->addJoins($table, $field, $fieldData); + } + $map->addTable($table); + if (in_array($data['name'], $this->apiEntities)) { + $this->addCustomFields($map, $table, $data['name']); + } + } + + $this->addBackReferences($map); + } + + /** + * @param Table $table + * @param string $field + * @param array $data + */ + private function addJoins(Table $table, $field, array $data) { + $fkClass = UtilsArray::value('FKClassName', $data); + + // can there be multiple methods e.g. pseudoconstant and fkclass + if ($fkClass) { + $tableName = AllCoreTables::getTableForClass($fkClass); + $fkKey = UtilsArray::value('FKKeyColumn', $data, 'id'); + $alias = str_replace('_id', '', $field); + $joinable = new Joinable($tableName, $fkKey, $alias); + $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE); + $table->addTableLink($field, $joinable); + } + elseif (UtilsArray::value('pseudoconstant', $data)) { + $this->addPseudoConstantJoin($table, $field, $data); + } + } + + /** + * @param Table $table + * @param string $field + * @param array $data + */ + private function addPseudoConstantJoin(Table $table, $field, array $data) { + $pseudoConstant = UtilsArray::value('pseudoconstant', $data); + $tableName = UtilsArray::value('table', $pseudoConstant); + $optionGroupName = UtilsArray::value('optionGroupName', $pseudoConstant); + $keyColumn = UtilsArray::value('keyColumn', $pseudoConstant, 'id'); + + if ($tableName) { + $alias = str_replace('civicrm_', '', $tableName); + $joinable = new Joinable($tableName, $keyColumn, $alias); + $condition = UtilsArray::value('condition', $pseudoConstant); + if ($condition) { + $joinable->addCondition($condition); + } + $table->addTableLink($field, $joinable); + } + elseif ($optionGroupName) { + $keyColumn = UtilsArray::value('keyColumn', $pseudoConstant, 'value'); + $joinable = new OptionValueJoinable($optionGroupName, NULL, $keyColumn); + + if (!empty($data['serialize'])) { + $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY); + } + + $table->addTableLink($field, $joinable); + } + } + + /** + * Loop through existing links and provide link from the other side + * + * @param SchemaMap $map + */ + private function addBackReferences(SchemaMap $map) { + foreach ($map->getTables() as $table) { + foreach ($table->getTableLinks() as $link) { + // there are too many possible joins from option value so skip + if ($link instanceof OptionValueJoinable) { + continue; + } + + $target = $map->getTableByName($link->getTargetTable()); + $tableName = $link->getBaseTable(); + $plural = str_replace('civicrm_', '', $this->getPlural($tableName)); + $joinable = new Joinable($tableName, $link->getBaseColumn(), $plural); + $joinable->setJoinType($joinable::JOIN_TYPE_ONE_TO_MANY); + $target->addTableLink($link->getTargetColumn(), $joinable); + } + } + } + + /** + * Simple implementation of pluralization. + * Could be replaced with symfony/inflector + * + * @param string $singular + * + * @return string + */ + private function getPlural($singular) { + $last_letter = substr($singular, -1); + switch ($last_letter) { + case 'y': + return substr($singular, 0, -1) . 'ies'; + + case 's': + return $singular . 'es'; + + default: + return $singular . 's'; + } + } + + /** + * @param \Civi\Api4\Service\Schema\SchemaMap $map + * @param \Civi\Api4\Service\Schema\Table $baseTable + * @param string $entity + */ + private function addCustomFields(SchemaMap $map, Table $baseTable, $entity) { + // Don't be silly + if (!array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) { + return; + } + $queryEntity = (array) $entity; + if ($entity == 'Contact') { + $queryEntity = ['Contact', 'Individual', 'Organization', 'Household']; + } + $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f') + ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id') + ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'label', 'column_name', 'option_group_id']) + ->where('g.extends IN (@entity)', ['@entity' => $queryEntity]) + ->where('g.is_active') + ->where('f.is_active') + ->execute(); + + $links = []; + + while ($fieldData->fetch()) { + $tableName = $fieldData->table_name; + + $customTable = $map->getTableByName($tableName); + if (!$customTable) { + $customTable = new Table($tableName); + } + + if (!empty($fieldData->option_group_id)) { + $optionValueJoinable = new OptionValueJoinable($fieldData->option_group_id, $fieldData->label); + $customTable->addTableLink($fieldData->column_name, $optionValueJoinable); + } + + $map->addTable($customTable); + + $alias = $fieldData->custom_group_name; + $links[$alias]['tableName'] = $tableName; + $links[$alias]['isMultiple'] = !empty($fieldData->is_multiple); + $links[$alias]['columns'][$fieldData->name] = $fieldData->column_name; + } + + foreach ($links as $alias => $link) { + $joinable = new CustomGroupJoinable($link['tableName'], $alias, $link['isMultiple'], $entity, $link['columns']); + $baseTable->addTableLink('id', $joinable); + } + } + +} diff --git a/Civi/Api4/Service/Schema/Table.php b/Civi/Api4/Service/Schema/Table.php new file mode 100644 index 000000000000..a5a81820aeb1 --- /dev/null +++ b/Civi/Api4/Service/Schema/Table.php @@ -0,0 +1,163 @@ +name = $name; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) { + $this->name = $name; + + return $this; + } + + /** + * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] + */ + public function getTableLinks() { + return $this->tableLinks; + } + + /** + * @return \Civi\Api4\Service\Schema\Joinable\Joinable[] + * Only those links that are not joining the table to itself + */ + public function getExternalLinks() { + return array_filter($this->tableLinks, function (Joinable $joinable) { + return $joinable->getTargetTable() !== $this->getName(); + }); + } + + /** + * @param \Civi\Api4\Service\Schema\Joinable\Joinable $linkToRemove + */ + public function removeLink(Joinable $linkToRemove) { + foreach ($this->tableLinks as $index => $link) { + if ($link === $linkToRemove) { + unset($this->tableLinks[$index]); + } + } + } + + /** + * @param string $baseColumn + * @param \Civi\Api4\Service\Schema\Joinable\Joinable $joinable + * + * @return $this + */ + public function addTableLink($baseColumn, Joinable $joinable) { + $target = $joinable->getTargetTable(); + $targetCol = $joinable->getTargetColumn(); + $alias = $joinable->getAlias(); + + if (!$this->hasLink($target, $targetCol, $alias)) { + if (!$joinable->getBaseTable()) { + $joinable->setBaseTable($this->getName()); + } + if (!$joinable->getBaseColumn()) { + $joinable->setBaseColumn($baseColumn); + } + $this->tableLinks[] = $joinable; + } + + return $this; + } + + /** + * @param mixed $tableLinks + * + * @return $this + */ + public function setTableLinks($tableLinks) { + $this->tableLinks = $tableLinks; + + return $this; + } + + /** + * @param $target + * @param $targetCol + * @param $alias + * + * @return bool + */ + private function hasLink($target, $targetCol, $alias) { + foreach ($this->tableLinks as $link) { + if ($link->getTargetTable() === $target + && $link->getTargetColumn() === $targetCol + && $link->getAlias() === $alias + ) { + return TRUE; + } + } + + return FALSE; + } + +} diff --git a/Civi/Api4/Service/Spec/CustomFieldSpec.php b/Civi/Api4/Service/Spec/CustomFieldSpec.php new file mode 100644 index 000000000000..d3f94232efa8 --- /dev/null +++ b/Civi/Api4/Service/Spec/CustomFieldSpec.php @@ -0,0 +1,153 @@ +setFkEntity('Contact'); + $dataType = 'Integer'; + break; + + case 'File': + case 'StateProvince': + case 'Country': + $this->setFkEntity($dataType); + $dataType = 'Integer'; + break; + } + return parent::setDataType($dataType); + } + + /** + * @return int + */ + public function getCustomFieldId() { + return $this->customFieldId; + } + + /** + * @param int $customFieldId + * + * @return $this + */ + public function setCustomFieldId($customFieldId) { + $this->customFieldId = $customFieldId; + + return $this; + } + + /** + * @return int + */ + public function getCustomGroupName() { + return $this->customGroup; + } + + /** + * @param string $customGroupName + * + * @return $this + */ + public function setCustomGroupName($customGroupName) { + $this->customGroup = $customGroupName; + + return $this; + } + + /** + * @return string + */ + public function getCustomTableName() { + return $this->tableName; + } + + /** + * @param string $customFieldColumnName + * + * @return $this + */ + public function setCustomTableName($customFieldColumnName) { + $this->tableName = $customFieldColumnName; + + return $this; + } + + /** + * @return string + */ + public function getCustomFieldColumnName() { + return $this->columnName; + } + + /** + * @param string $customFieldColumnName + * + * @return $this + */ + public function setCustomFieldColumnName($customFieldColumnName) { + $this->columnName = $customFieldColumnName; + + return $this; + } + +} diff --git a/Civi/Api4/Service/Spec/FieldSpec.php b/Civi/Api4/Service/Spec/FieldSpec.php new file mode 100644 index 000000000000..7a44a1d925c6 --- /dev/null +++ b/Civi/Api4/Service/Spec/FieldSpec.php @@ -0,0 +1,402 @@ + 'Integer', + 'Link' => 'Url', + 'Memo' => 'Text', + ]; + + /** + * @param string $name + * @param string $entity + * @param string $dataType + */ + public function __construct($name, $entity, $dataType = 'String') { + $this->entity = $entity; + $this->setName($name); + $this->setDataType($dataType); + } + + /** + * @return mixed + */ + public function getDefaultValue() { + return $this->defaultValue; + } + + /** + * @param mixed $defaultValue + * + * @return $this + */ + public function setDefaultValue($defaultValue) { + $this->defaultValue = $defaultValue; + + return $this; + } + + /** + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * @param string $name + * + * @return $this + */ + public function setName($name) { + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * @param string $title + * + * @return $this + */ + public function setTitle($title) { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getEntity() { + return $this->entity; + } + + /** + * @return string + */ + public function getDescription() { + return $this->description; + } + + /** + * @param string $description + * + * @return $this + */ + public function setDescription($description) { + $this->description = $description; + + return $this; + } + + /** + * @return bool + */ + public function isRequired() { + return $this->required; + } + + /** + * @param bool $required + * + * @return $this + */ + public function setRequired($required) { + $this->required = $required; + + return $this; + } + + /** + * @return bool + */ + public function getRequiredIf() { + return $this->requiredIf; + } + + /** + * @param bool $requiredIf + * + * @return $this + */ + public function setRequiredIf($requiredIf) { + $this->requiredIf = $requiredIf; + + return $this; + } + + /** + * @return string + */ + public function getDataType() { + return $this->dataType; + } + + /** + * @param $dataType + * + * @return $this + * @throws \Exception + */ + public function setDataType($dataType) { + if (array_key_exists($dataType, self::$typeAliases)) { + $dataType = self::$typeAliases[$dataType]; + } + + if (!in_array($dataType, $this->getValidDataTypes())) { + throw new \Exception(sprintf('Invalid data type "%s', $dataType)); + } + + $this->dataType = $dataType; + + return $this; + } + + /** + * @return int + */ + public function getSerialize() { + return $this->serialize; + } + + /** + * @param int|null $serialize + * @return $this + */ + public function setSerialize($serialize) { + $this->serialize = $serialize; + + return $this; + } + + /** + * @return string + */ + public function getInputType() { + return $this->inputType; + } + + /** + * @param string $inputType + * @return $this + */ + public function setInputType($inputType) { + $this->inputType = $inputType; + + return $this; + } + + /** + * @return array + */ + public function getInputAttrs() { + return $this->inputAttrs; + } + + /** + * @param array $inputAttrs + * @return $this + */ + public function setInputAttrs($inputAttrs) { + $this->inputAttrs = $inputAttrs; + + return $this; + } + + /** + * Add valid types that are not not part of \CRM_Utils_Type::dataTypes + * + * @return array + */ + private function getValidDataTypes() { + $extraTypes = ['Boolean', 'Text', 'Float', 'Url', 'Array']; + $extraTypes = array_combine($extraTypes, $extraTypes); + + return array_merge(\CRM_Utils_Type::dataTypes(), $extraTypes); + } + + /** + * @return array + */ + public function getOptions() { + if (!isset($this->options) || $this->options === TRUE) { + $fieldName = $this->getName(); + + if ($this instanceof CustomFieldSpec) { + // buildOptions relies on the custom_* type of field names + $fieldName = sprintf('custom_%d', $this->getCustomFieldId()); + } + + $bao = CoreUtil::getBAOFromApiName($this->getEntity()); + $options = $bao::buildOptions($fieldName); + + if (!is_array($options) || !$options) { + $options = FALSE; + } + + $this->setOptions($options); + } + return $this->options; + } + + /** + * @param array|bool $options + * + * @return $this + */ + public function setOptions($options) { + $this->options = $options; + return $this; + } + + /** + * @return string + */ + public function getFkEntity() { + return $this->fkEntity; + } + + /** + * @param string $fkEntity + * + * @return $this + */ + public function setFkEntity($fkEntity) { + $this->fkEntity = $fkEntity; + + return $this; + } + + /** + * @param array $values + * @return array + */ + public function toArray($values = []) { + $ret = []; + foreach (get_object_vars($this) as $key => $val) { + $key = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $key)); + if (!$values || in_array($key, $values)) { + $ret[$key] = $val; + } + } + return $ret; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ACLCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ACLCreationSpecProvider.php new file mode 100644 index 000000000000..856b6cf47cbf --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ACLCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('entity_table')->setDefaultValue('civicrm_acl_role'); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'ACL' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php new file mode 100644 index 000000000000..833d96a82159 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ActionScheduleCreationSpecProvider.php @@ -0,0 +1,62 @@ +getFieldByName('title')->setRequired(TRUE); + $spec->getFieldByName('mapping_id')->setRequired(TRUE); + $spec->getFieldByName('entity_value')->setRequired(TRUE); + $spec->getFieldByName('start_action_date')->setRequiredIf('empty($values.absolute_date)'); + $spec->getFieldByName('absolute_date')->setRequiredIf('empty($values.start_action_date)'); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'ActionSchedule' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php new file mode 100644 index 000000000000..4e59727ac0d0 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ActivityCreationSpecProvider.php @@ -0,0 +1,63 @@ +setRequired(TRUE); + $sourceContactField->setFkEntity('Contact'); + + $spec->addFieldSpec($sourceContactField); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Activity' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php new file mode 100644 index 000000000000..3febce659cef --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/AddressCreationSpecProvider.php @@ -0,0 +1,62 @@ +getFieldByName('contact_id')->setRequired(TRUE); + $spec->getFieldByName('location_type_id')->setRequired(TRUE); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'Address' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/CampaignCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CampaignCreationSpecProvider.php new file mode 100644 index 000000000000..9e39553e807f --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/CampaignCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('title')->setRequired(TRUE); + $spec->getFieldByName('name')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Campaign' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php new file mode 100644 index 000000000000..d57817da9b3a --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ContactCreationSpecProvider.php @@ -0,0 +1,66 @@ +getFieldByName('contact_type') + ->setDefaultValue('Individual'); + + $spec->getFieldByName('is_opt_out')->setRequired(FALSE); + $spec->getFieldByName('is_deleted')->setRequired(FALSE); + + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'Contact' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php new file mode 100644 index 000000000000..d211c0218d3f --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ContactTypeCreationSpecProvider.php @@ -0,0 +1,64 @@ +getFieldByName('label')->setRequired(TRUE); + $spec->getFieldByName('name')->setRequired(TRUE); + $spec->getFieldByName('parent_id')->setRequired(TRUE); + + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'ContactType' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php new file mode 100644 index 000000000000..4ad937b5b585 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/ContributionCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('financial_type_id')->setRequired(TRUE); + $spec->getFieldByName('receive_date')->setDefaultValue('now'); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Contribution' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/CustomFieldCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CustomFieldCreationSpecProvider.php new file mode 100644 index 000000000000..a0921efc8172 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/CustomFieldCreationSpecProvider.php @@ -0,0 +1,63 @@ +getEntity(), 'Array'); + $optionField->setTitle(ts('Option Values')); + $optionField->setDescription('Pass an array of options (value => label) to create this field\'s option values'); + $spec->addFieldSpec($optionField); + $spec->getFieldByName('data_type')->setDefaultValue('String')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'CustomField' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php new file mode 100644 index 000000000000..2a2555a1356e --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('extends')->setRequired(TRUE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'CustomGroup' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php new file mode 100644 index 000000000000..3d082b6cd267 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/CustomValueSpecProvider.php @@ -0,0 +1,69 @@ +getAction(); + if ($action !== 'create') { + $idField = new FieldSpec('id', $spec->getEntity(), 'Integer'); + $idField->setTitle(ts('Custom Value ID')); + $spec->addFieldSpec($idField); + } + $entityField = new FieldSpec('entity_id', $spec->getEntity(), 'Integer'); + $entityField->setTitle(ts('Entity ID')); + $entityField->setRequired($action === 'create'); + $entityField->setFkEntity('Contact'); + $spec->addFieldSpec($entityField); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return strstr($entity, 'Custom_'); + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/DefaultLocationTypeProvider.php b/Civi/Api4/Service/Spec/Provider/DefaultLocationTypeProvider.php new file mode 100644 index 000000000000..908450d9d8bb --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/DefaultLocationTypeProvider.php @@ -0,0 +1,62 @@ +getFieldByName('location_type_id')->setRequired(TRUE); + $defaultType = \CRM_Core_BAO_LocationType::getDefault(); + if ($defaultType) { + $locationField->setDefaultValue($defaultType->id); + } + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $action === 'create' && in_array($entity, ['Address', 'Email', 'IM', 'OpenID', 'Phone']); + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/DomainCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/DomainCreationSpecProvider.php new file mode 100644 index 000000000000..22bc5d4ae112 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/DomainCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('name')->setRequired(TRUE); + $spec->getFieldByName('version')->setRequired(TRUE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Domain' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php new file mode 100644 index 000000000000..fde8e0889eec --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/EmailCreationSpecProvider.php @@ -0,0 +1,61 @@ +getFieldByName('contact_id')->setRequired(TRUE); + $spec->getFieldByName('email')->setRequired(TRUE); + $spec->getFieldByName('on_hold')->setRequired(FALSE); + $spec->getFieldByName('is_bulkmail')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Email' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/EntityTagCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/EntityTagCreationSpecProvider.php new file mode 100644 index 000000000000..cc0f5b6048f7 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/EntityTagCreationSpecProvider.php @@ -0,0 +1,61 @@ +getFieldByName('entity_table')->setRequired(FALSE)->setDefaultValue('civicrm_contact'); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'EntityTag' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php new file mode 100644 index 000000000000..301a8d80450b --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/EventCreationSpecProvider.php @@ -0,0 +1,68 @@ +getFieldByName('event_type_id')->setRequiredIf('empty($values.template_id)'); + $spec->getFieldByName('title')->setRequiredIf('empty($values.is_template)'); + $spec->getFieldByName('start_date')->setRequiredIf('empty($values.is_template)'); + $spec->getFieldByName('template_title')->setRequiredIf('!empty($values.is_template)'); + + $template_id = new FieldSpec('template_id', 'Event', 'Integer'); + $template_id + ->setTitle('Template Id') + ->setDescription('Template on which to base this new event'); + $spec->addFieldSpec($template_id); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Event' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/Generic/SpecProviderInterface.php b/Civi/Api4/Service/Spec/Provider/Generic/SpecProviderInterface.php new file mode 100644 index 000000000000..35dc09b2e19e --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/Generic/SpecProviderInterface.php @@ -0,0 +1,59 @@ +getFieldByName('is_deleted'); + if ($isDeletedField) { + $isDeletedField->setDefaultValue('0'); + } + + // Exclude test records from api Get by default + $isTestField = $spec->getFieldByName('is_test'); + if ($isTestField) { + $isTestField->setDefaultValue('0'); + } + + $isTemplateField = $spec->getFieldByName('is_template'); + if ($isTemplateField) { + $isTemplateField->setDefaultValue('0'); + } + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $action === 'get'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php new file mode 100644 index 000000000000..975296fb5695 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/GroupCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('title')->setRequired(TRUE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Group' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/MappingCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/MappingCreationSpecProvider.php new file mode 100644 index 000000000000..7058659171d5 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/MappingCreationSpecProvider.php @@ -0,0 +1,63 @@ +getFieldByName('name')->setRequired(TRUE); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return strpos($entity, 'Mapping') === 0 && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/NavigationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/NavigationSpecProvider.php new file mode 100644 index 000000000000..2af06eb99724 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/NavigationSpecProvider.php @@ -0,0 +1,60 @@ +getFieldByName('domain_id')->setRequired(FALSE)->setDefaultValue('current_domain'); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Navigation' && in_array($action, ['create', 'get']); + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php new file mode 100644 index 000000000000..1b045396110e --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/NoteCreationSpecProvider.php @@ -0,0 +1,62 @@ +getFieldByName('note')->setRequired(TRUE); + $spec->getFieldByName('entity_table')->setDefaultValue('civicrm_contact'); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'Note' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php new file mode 100644 index 000000000000..f46859f8d1a1 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/OptionValueCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('weight')->setRequired(FALSE); + $spec->getFieldByName('value')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'OptionValue' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php new file mode 100644 index 000000000000..1e259ce17ee0 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/PhoneCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('contact_id')->setRequired(TRUE); + $spec->getFieldByName('phone')->setRequired(TRUE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'Phone' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/RelationshipTypeCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/RelationshipTypeCreationSpecProvider.php new file mode 100644 index 000000000000..ca390bd79f8e --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/RelationshipTypeCreationSpecProvider.php @@ -0,0 +1,59 @@ +getFieldByName('name_a_b')->setRequired(TRUE); + $spec->getFieldByName('name_b_a')->setRequired(TRUE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'RelationshipType' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/StatusPreferenceCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/StatusPreferenceCreationSpecProvider.php new file mode 100644 index 000000000000..369137de7286 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/StatusPreferenceCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('domain_id')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'StatusPreference' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/TagCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/TagCreationSpecProvider.php new file mode 100644 index 000000000000..c87678765645 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/TagCreationSpecProvider.php @@ -0,0 +1,61 @@ +getFieldByName('used_for')->setDefaultValue('civicrm_contact'); + } + + /** + * @param string $entity + * @param string $action + * + * @return bool + */ + public function applies($entity, $action) { + return $entity === 'Tag' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/UFFieldCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/UFFieldCreationSpecProvider.php new file mode 100644 index 000000000000..f0ae2b10e7ab --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/UFFieldCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('label')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'UFField' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/Provider/UFMatchCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/UFMatchCreationSpecProvider.php new file mode 100644 index 000000000000..450aa3c5ec64 --- /dev/null +++ b/Civi/Api4/Service/Spec/Provider/UFMatchCreationSpecProvider.php @@ -0,0 +1,58 @@ +getFieldByName('domain_id')->setRequired(FALSE); + } + + /** + * @inheritDoc + */ + public function applies($entity, $action) { + return $entity === 'UFMatch' && $action === 'create'; + } + +} diff --git a/Civi/Api4/Service/Spec/RequestSpec.php b/Civi/Api4/Service/Spec/RequestSpec.php new file mode 100644 index 000000000000..22bc46de1ef0 --- /dev/null +++ b/Civi/Api4/Service/Spec/RequestSpec.php @@ -0,0 +1,145 @@ +entity = $entity; + $this->action = $action; + } + + public function addFieldSpec(FieldSpec $field) { + $this->fields[] = $field; + } + + /** + * @param $name + * + * @return FieldSpec|null + */ + public function getFieldByName($name) { + foreach ($this->fields as $field) { + if ($field->getName() === $name) { + return $field; + } + } + + return NULL; + } + + /** + * @return array + * Gets all the field names currently part of the specification + */ + public function getFieldNames() { + return array_map(function(FieldSpec $field) { + return $field->getName(); + }, $this->fields); + } + + /** + * @return array|FieldSpec[] + */ + public function getRequiredFields() { + return array_filter($this->fields, function (FieldSpec $field) { + return $field->isRequired(); + }); + } + + /** + * @return array|FieldSpec[] + */ + public function getConditionalRequiredFields() { + return array_filter($this->fields, function (FieldSpec $field) { + return $field->getRequiredIf(); + }); + } + + /** + * @param array $fieldNames + * Optional array of fields to return + * @return FieldSpec[] + */ + public function getFields($fieldNames = NULL) { + if (!$fieldNames) { + return $this->fields; + } + $fields = []; + foreach ($this->fields as $field) { + if (in_array($field->getName(), $fieldNames)) { + $fields[] = $field; + } + } + return $fields; + } + + /** + * @return string + */ + public function getEntity() { + return $this->entity; + } + + /** + * @return string + */ + public function getAction() { + return $this->action; + } + +} diff --git a/Civi/Api4/Service/Spec/SpecFormatter.php b/Civi/Api4/Service/Spec/SpecFormatter.php new file mode 100644 index 000000000000..b1c1c804e3d2 --- /dev/null +++ b/Civi/Api4/Service/Spec/SpecFormatter.php @@ -0,0 +1,251 @@ +getOptions(); + } + $fieldArray[$field->getName()] = $field->toArray(); + } + + return $fieldArray; + } + + /** + * @param array $data + * @param string $entity + * + * @return FieldSpec + */ + public static function arrayToField(array $data, $entity) { + $dataTypeName = self::getDataType($data); + + if (!empty($data['custom_group_id'])) { + $field = new CustomFieldSpec($data['name'], $entity, $dataTypeName); + if (strpos($entity, 'Custom_') !== 0) { + $field->setName($data['custom_group.name'] . '.' . $data['name']); + } + else { + $field->setCustomTableName($data['custom_group.table_name']); + $field->setCustomFieldColumnName($data['column_name']); + } + $field->setCustomFieldId(ArrayHelper::value('id', $data)); + $field->setCustomGroupName($data['custom_group.name']); + $field->setTitle(ArrayHelper::value('label', $data)); + $field->setOptions(self::customFieldHasOptions($data)); + if (\CRM_Core_BAO_CustomField::isSerialized($data)) { + $field->setSerialize(\CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND); + } + } + else { + $name = ArrayHelper::value('name', $data); + $field = new FieldSpec($name, $entity, $dataTypeName); + $field->setRequired((bool) ArrayHelper::value('required', $data, FALSE)); + $field->setTitle(ArrayHelper::value('title', $data)); + $field->setOptions(!empty($data['pseudoconstant'])); + $field->setSerialize(ArrayHelper::value('serialize', $data)); + } + + $field->setDefaultValue(ArrayHelper::value('default', $data)); + $field->setDescription(ArrayHelper::value('description', $data)); + self::setInputTypeAndAttrs($field, $data, $dataTypeName); + + $fkAPIName = ArrayHelper::value('FKApiName', $data); + $fkClassName = ArrayHelper::value('FKClassName', $data); + if ($fkAPIName || $fkClassName) { + $field->setFkEntity($fkAPIName ?: AllCoreTables::getBriefName($fkClassName)); + } + + return $field; + } + + /** + * Does this custom field have options + * + * @param array $field + * @return bool + */ + private static function customFieldHasOptions($field) { + // This will include boolean fields with Yes/No options. + if (in_array($field['html_type'], ['Radio', 'CheckBox'])) { + return TRUE; + } + // Do this before the "Select" string search because date fields have a "Select Date" html_type + // and contactRef fields have an "Autocomplete-Select" html_type - contacts are an FK not an option list. + if (in_array($field['data_type'], ['ContactReference', 'Date'])) { + return FALSE; + } + if (strpos($field['html_type'], 'Select') !== FALSE) { + return TRUE; + } + return !empty($field['option_group_id']); + } + + /** + * Get the data type from an array. Defaults to 'data_type' with fallback to + * mapping for the integer value 'type' + * + * @param array $data + * + * @return string + */ + private static function getDataType(array $data) { + if (isset($data['data_type'])) { + return !empty($data['time_format']) ? 'Timestamp' : $data['data_type']; + } + + $dataTypeInt = ArrayHelper::value('type', $data); + $dataTypeName = \CRM_Utils_Type::typeToString($dataTypeInt); + + return $dataTypeName; + } + + /** + * @param \Civi\Api4\Service\Spec\FieldSpec $fieldSpec + * @param array $data + * @param string $dataTypeName + */ + public static function setInputTypeAndAttrs(FieldSpec &$fieldSpec, $data, $dataTypeName) { + $inputType = isset($data['html']['type']) ? $data['html']['type'] : ArrayHelper::value('html_type', $data); + $inputAttrs = ArrayHelper::value('html', $data, []); + unset($inputAttrs['type']); + + if (!$inputType) { + // If no html type is set, guess + switch ($dataTypeName) { + case 'Int': + $inputType = 'Number'; + $inputAttrs['min'] = 0; + break; + + case 'Text': + $inputType = ArrayHelper::value('type', $data) === \CRM_Utils_Type::T_LONGTEXT ? 'TextArea' : 'Text'; + break; + + case 'Timestamp': + $inputType = 'Date'; + $inputAttrs['time'] = TRUE; + break; + + case 'Date': + $inputAttrs['time'] = FALSE; + break; + + case 'Time': + $inputType = 'Date'; + $inputAttrs['time'] = TRUE; + $inputAttrs['date'] = FALSE; + break; + + default: + $map = [ + 'Email' => 'Email', + 'Boolean' => 'Checkbox', + ]; + $inputType = ArrayHelper::value($dataTypeName, $map, 'Text'); + } + } + if (strstr($inputType, 'Multi-Select') || ($inputType == 'Select' && !empty($data['serialize']))) { + $inputAttrs['multiple'] = TRUE; + $inputType = 'Select'; + } + $map = [ + 'Select State/Province' => 'Select', + 'Select Country' => 'Select', + 'Select Date' => 'Date', + 'Link' => 'Url', + ]; + $inputType = ArrayHelper::value($inputType, $map, $inputType); + if ($inputType == 'Date' && !empty($inputAttrs['formatType'])) { + self::setLegacyDateFormat($inputAttrs); + } + // Date/time settings from custom fields + if ($inputType == 'Date' && !empty($data['custom_group_id'])) { + $inputAttrs['time'] = empty($data['time_format']) ? FALSE : ($data['time_format'] == 1 ? 12 : 24); + $inputAttrs['date'] = $data['date_format']; + $inputAttrs['start_date_years'] = (int) $data['start_date_years']; + $inputAttrs['end_date_years'] = (int) $data['end_date_years']; + } + if ($inputType == 'Text' && !empty($data['maxlength'])) { + $inputAttrs['maxlength'] = (int) $data['maxlength']; + } + if ($inputType == 'TextArea') { + foreach (['rows', 'cols', 'note_rows', 'note_cols'] as $prop) { + if (!empty($data[$prop])) { + $inputAttrs[str_replace('note_', '', $prop)] = (int) $data[$prop]; + } + } + } + $fieldSpec + ->setInputType($inputType) + ->setInputAttrs($inputAttrs); + } + + /** + * @param array $inputAttrs + */ + private static function setLegacyDateFormat(&$inputAttrs) { + if (empty(\Civi::$statics['legacyDatePrefs'][$inputAttrs['formatType']])) { + \Civi::$statics['legacyDatePrefs'][$inputAttrs['formatType']] = []; + $params = ['name' => $inputAttrs['formatType']]; + \CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_PreferencesDate', $params, \Civi::$statics['legacyDatePrefs'][$inputAttrs['formatType']]); + } + $dateFormat = \Civi::$statics['legacyDatePrefs'][$inputAttrs['formatType']]; + unset($inputAttrs['formatType']); + $inputAttrs['time'] = !empty($dateFormat['time_format']); + $inputAttrs['date'] = TRUE; + $inputAttrs['start_date_years'] = (int) $dateFormat['start']; + $inputAttrs['end_date_years'] = (int) $dateFormat['end']; + } + +} diff --git a/Civi/Api4/Service/Spec/SpecGatherer.php b/Civi/Api4/Service/Spec/SpecGatherer.php new file mode 100644 index 000000000000..d9b6e0e90c6f --- /dev/null +++ b/Civi/Api4/Service/Spec/SpecGatherer.php @@ -0,0 +1,175 @@ +addDAOFields($entity, $action, $specification); + if ($includeCustom && array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) { + $this->addCustomFields($entity, $specification); + } + } + // Custom pseudo-entities + else { + $this->getCustomGroupFields(substr($entity, 7), $specification); + } + + // Default value only makes sense for create actions + if ($action != 'create') { + foreach ($specification->getFields() as $field) { + $field->setDefaultValue(NULL); + } + } + + foreach ($this->specProviders as $provider) { + if ($provider->applies($entity, $action)) { + $provider->modifySpec($specification); + } + } + + return $specification; + } + + /** + * @param \Civi\Api4\Service\Spec\Provider\Generic\SpecProviderInterface $provider + */ + public function addSpecProvider(SpecProviderInterface $provider) { + $this->specProviders[] = $provider; + } + + /** + * @param string $entity + * @param string $action + * @param \Civi\Api4\Service\Spec\RequestSpec $specification + */ + private function addDAOFields($entity, $action, RequestSpec $specification) { + $DAOFields = $this->getDAOFields($entity); + + foreach ($DAOFields as $DAOField) { + if ($DAOField['name'] == 'id' && $action == 'create') { + continue; + } + if ($action !== 'create' || isset($DAOField['default'])) { + $DAOField['required'] = FALSE; + } + if ($DAOField['name'] == 'is_active' && empty($DAOField['default'])) { + $DAOField['default'] = '1'; + } + $field = SpecFormatter::arrayToField($DAOField, $entity); + $specification->addFieldSpec($field); + } + } + + /** + * @param string $entity + * @param \Civi\Api4\Service\Spec\RequestSpec $specification + */ + private function addCustomFields($entity, RequestSpec $specification) { + $extends = ($entity == 'Contact') ? ['Contact', 'Individual', 'Organization', 'Household'] : [$entity]; + $customFields = CustomField::get() + ->setCheckPermissions(FALSE) + ->addWhere('custom_group.extends', 'IN', $extends) + ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value', 'date_format', 'time_format', 'start_date_years', 'end_date_years']) + ->execute(); + + foreach ($customFields as $fieldArray) { + $field = SpecFormatter::arrayToField($fieldArray, $entity); + $specification->addFieldSpec($field); + } + } + + /** + * @param string $customGroup + * @param \Civi\Api4\Service\Spec\RequestSpec $specification + */ + private function getCustomGroupFields($customGroup, RequestSpec $specification) { + $customFields = CustomField::get() + ->addWhere('custom_group.name', '=', $customGroup) + ->setSelect(['custom_group.name', 'custom_group_id', 'name', 'label', 'data_type', 'html_type', 'is_searchable', 'is_search_range', 'weight', 'is_active', 'is_view', 'option_group_id', 'default_value', 'custom_group.table_name', 'column_name', 'date_format', 'time_format', 'start_date_years', 'end_date_years']) + ->execute(); + + foreach ($customFields as $fieldArray) { + $field = SpecFormatter::arrayToField($fieldArray, 'Custom_' . $customGroup); + $specification->addFieldSpec($field); + } + } + + /** + * @param string $entityName + * + * @return array + */ + private function getDAOFields($entityName) { + $bao = CoreUtil::getBAOFromApiName($entityName); + + return $bao::fields(); + } + +} diff --git a/Civi/Api4/Setting.php b/Civi/Api4/Setting.php new file mode 100644 index 000000000000..47e7a569fbfb --- /dev/null +++ b/Civi/Api4/Setting.php @@ -0,0 +1,65 @@ + is_multiple ], ..] + * @param mixed $values + * The value to be inserted + */ + public static function insert(&$array, $parts, $values) { + $key = key($parts); + $isMulti = array_shift($parts); + if (!isset($array[$key])) { + $array[$key] = $isMulti ? [] : NULL; + } + if (empty($parts)) { + $values = self::filterValues($array, $isMulti, $values); + $array[$key] = $values; + } + else { + if ($isMulti) { + foreach ($array[$key] as &$subArray) { + self::insert($subArray, $parts, $values); + } + } + else { + self::insert($array[$key], $parts, $values); + } + } + } + + /** + * @param $parentArray + * @param $isMulti + * @param $values + * + * @return array|mixed + */ + private static function filterValues($parentArray, $isMulti, $values) { + $parentID = UtilsArray::value('id', $parentArray); + + if ($parentID) { + $values = array_filter($values, function ($value) use ($parentID) { + return UtilsArray::value('_parent_id', $value) == $parentID; + }); + } + + $unsets = ['_parent_id', '_base_id']; + array_walk($values, function (&$value) use ($unsets) { + foreach ($unsets as $unset) { + if (isset($value[$unset])) { + unset($value[$unset]); + } + } + }); + + if (!$isMulti) { + $values = array_shift($values); + } + return $values; + } + +} diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php new file mode 100644 index 000000000000..56eee46b42a8 --- /dev/null +++ b/Civi/Api4/Utils/CoreUtil.php @@ -0,0 +1,88 @@ +addSelect('table_name') + ->addWhere('name', '=', $customGroupName) + ->execute() + ->first()['table_name']; + } + + /** + * Given a sql table name, return the name of the api entity. + * + * @param $tableName + * @return string + */ + public static function getApiNameFromTableName($tableName) { + return AllCoreTables::getBriefName(AllCoreTables::getClassForTable($tableName)); + } + +} diff --git a/Civi/Api4/Utils/FormattingUtil.php b/Civi/Api4/Utils/FormattingUtil.php new file mode 100644 index 000000000000..b845ea660843 --- /dev/null +++ b/Civi/Api4/Utils/FormattingUtil.php @@ -0,0 +1,132 @@ + $field) { + if (!empty($params[$name])) { + $value =& $params[$name]; + // Hack for null values -- see comment below + if ($value === 'null') { + $value = 'Null'; + } + FormattingUtil::formatValue($value, $field, $entity); + // Ensure we have an array for serialized fields + if (!empty($field['serialize'] && !is_array($value))) { + $value = (array) $value; + } + } + /* + * Because of the wacky way that database values are saved we need to format + * some of the values here. In this strange world the string 'null' is used to + * unset values. Hence if we encounter true null we change it to string 'null'. + * + * If we encounter the string 'null' then we assume the user actually wants to + * set the value to string null. However since the string null is reserved for + * unsetting values we must change it. Another quirk of the DB_DataObject is + * that it allows 'Null' to be set, but any other variation of string 'null' + * will be converted to true null, e.g. 'nuLL', 'NUlL' etc. so we change it to + * 'Null'. + */ + elseif (array_key_exists($name, $params) && $params[$name] === NULL) { + $params[$name] = 'null'; + } + } + } + + /** + * Transform raw api input to appropriate format for use in a SQL query. + * + * This is used by read AND write actions (Get, Create, Update, Replace) + * + * @param $value + * @param $fieldSpec + * @param string $entity + * Ex: 'Contact', 'Domain' + * @throws \API_Exception + */ + public static function formatValue(&$value, $fieldSpec, $entity) { + if (is_array($value)) { + foreach ($value as &$val) { + self::formatValue($val, $fieldSpec, $entity); + } + return; + } + $fk = UtilsArray::value('fk_entity', $fieldSpec); + if ($fieldSpec['name'] == 'id') { + $fk = $entity; + } + $dataType = UtilsArray::value('data_type', $fieldSpec); + + if ($fk === 'Domain' && $value === 'current_domain') { + $value = \CRM_Core_Config::domainID(); + } + + if ($fk === 'Contact' && !is_numeric($value)) { + $value = \_civicrm_api3_resolve_contactID($value); + if ('unknown-user' === $value) { + throw new \API_Exception("\"{$fieldSpec['name']}\" \"{$value}\" cannot be resolved to a contact ID", 2002, ['error_field' => $fieldSpec['name'], "type" => "integer"]); + } + } + + switch ($dataType) { + case 'Timestamp': + $value = date('Y-m-d H:i:s', strtotime($value)); + break; + + case 'Date': + $value = date('Ymd', strtotime($value)); + break; + } + } + +} diff --git a/Civi/Api4/Utils/ReflectionUtils.php b/Civi/Api4/Utils/ReflectionUtils.php new file mode 100644 index 000000000000..535f9523c5b5 --- /dev/null +++ b/Civi/Api4/Utils/ReflectionUtils.php @@ -0,0 +1,140 @@ +getDocComment()); + + // Recurse into parent functions + if (isset($docs['inheritDoc']) || isset($docs['inheritdoc'])) { + unset($docs['inheritDoc'], $docs['inheritdoc']); + $newReflection = NULL; + try { + if ($type) { + $name = $reflection->getName(); + $reflectionClass = $reflection->getDeclaringClass()->getParentClass(); + if ($reflectionClass) { + $getItem = "get$type"; + $newReflection = $reflectionClass->$getItem($name); + } + } + else { + $newReflection = $reflection->getParentClass(); + } + } + catch (\ReflectionException $e) { + } + if ($newReflection) { + // Mix in + $additionalDocs = self::getCodeDocs($newReflection, $type); + if (!empty($docs['comment']) && !empty($additionalDocs['comment'])) { + $docs['comment'] .= "\n\n" . $additionalDocs['comment']; + } + $docs += $additionalDocs; + } + } + return $docs; + } + + /** + * @param string $comment + * @return array + */ + public static function parseDocBlock($comment) { + $info = []; + foreach (preg_split("/((\r?\n)|(\r\n?))/", $comment) as $num => $line) { + if (!$num || strpos($line, '*/') !== FALSE) { + continue; + } + $line = ltrim(trim($line), '* '); + if (strpos($line, '@') === 0) { + $words = explode(' ', $line); + $key = substr($words[0], 1); + if ($key == 'var') { + $info['type'] = explode('|', $words[1]); + } + elseif ($key == 'options') { + $val = str_replace(', ', ',', implode(' ', array_slice($words, 1))); + $info['options'] = explode(',', $val); + } + else { + // Unrecognized annotation, but we'll duly add it to the info array + $val = implode(' ', array_slice($words, 1)); + $info[$key] = strlen($val) ? $val : TRUE; + } + } + elseif ($num == 1) { + $info['description'] = $line; + } + elseif (!$line) { + if (isset($info['comment'])) { + $info['comment'] .= "\n"; + } + } + else { + $info['comment'] = isset($info['comment']) ? "{$info['comment']}\n$line" : $line; + } + } + if (isset($info['comment'])) { + $info['comment'] = trim($info['comment']); + } + return $info; + } + + /** + * List all traits used by a class and its parents. + * + * @param object|string $class + * @return array + */ + public static function getTraits($class) { + $traits = []; + // Get traits of this class + parent classes + do { + $traits = array_merge(class_uses($class), $traits); + } while ($class = get_parent_class($class)); + // Get traits of traits + foreach ($traits as $trait => $same) { + $traits = array_merge(class_uses($trait), $traits); + } + return $traits; + } + +} diff --git a/Civi/Api4/Website.php b/Civi/Api4/Website.php new file mode 100644 index 000000000000..280c71d3fbf5 --- /dev/null +++ b/Civi/Api4/Website.php @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Civi/CCase/Analyzer.php b/Civi/CCase/Analyzer.php index bd67838321d8..12de74ab9fc4 100644 --- a/Civi/CCase/Analyzer.php +++ b/Civi/CCase/Analyzer.php @@ -1,9 +1,9 @@ getActivityIndex(array('activity_type_id', 'status_id')); - $activityTypeGroup = civicrm_api3('option_group', 'get', array('name' => 'activity_type')); - $activityType = array( + $idx = $this->getActivityIndex(['activity_type_id', 'status_id']); + $activityTypeGroup = civicrm_api3('option_group', 'get', ['name' => 'activity_type']); + $activityType = [ 'name' => $type, 'option_group_id' => $activityTypeGroup['id'], - ); + ]; $activityTypeID = civicrm_api3('option_value', 'get', $activityType); $activityTypeID = $activityTypeID['values'][$activityTypeID['id']]['value']; if ($status) { - $activityStatusGroup = civicrm_api3('option_group', 'get', array('name' => 'activity_status')); - $activityStatus = array( + $activityStatusGroup = civicrm_api3('option_group', 'get', ['name' => 'activity_status']); + $activityStatus = [ 'name' => $status, 'option_group_id' => $activityStatusGroup['id'], - ); + ]; $activityStatusID = civicrm_api3('option_value', 'get', $activityStatus); $activityStatusID = $activityStatusID['values'][$activityStatusID['id']]['value']; } @@ -115,13 +115,13 @@ public function getActivities() { if ($this->activities === NULL) { // TODO find batch-oriented API for getting all activities in a case $case = $this->getCase(); - $activities = array(); + $activities = []; if (isset($case['activities'])) { foreach ($case['activities'] as $actId) { - $result = civicrm_api3('Activity', 'get', array( + $result = civicrm_api3('Activity', 'get', [ 'id' => $actId, 'is_current_revision' => 1, - )); + ]); $activities = array_merge($activities, $result['values']); } } @@ -132,14 +132,15 @@ public function getActivities() { /** * Get a single activity record by type. + * This function is only used by SequenceListenerTest * * @param string $type * @throws \Civi\CCase\Exception\MultipleActivityException * @return array|NULL, activity record (api/v3) */ public function getSingleActivity($type) { - $idx = $this->getActivityIndex(array('activity_type_id', 'id')); - $actTypes = array_flip(\CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name')); + $idx = $this->getActivityIndex(['activity_type_id', 'id']); + $actTypes = array_flip(\CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate')); $typeId = $actTypes[$type]; $count = isset($idx[$typeId]) ? count($idx[$typeId]) : 0; @@ -168,7 +169,7 @@ public function getCaseId() { */ public function getCase() { if ($this->case === NULL) { - $this->case = civicrm_api3('case', 'getsingle', array('id' => $this->caseId)); + $this->case = civicrm_api3('case', 'getsingle', ['id' => $this->caseId]); } return $this->case; } @@ -222,7 +223,7 @@ public function flush() { $this->case = NULL; $this->caseType = NULL; $this->activities = NULL; - $this->indices = array(); + $this->indices = []; } } diff --git a/Civi/CCase/CaseChangeListener.php b/Civi/CCase/CaseChangeListener.php index 2236affdcd48..b473a090e03d 100644 --- a/Civi/CCase/CaseChangeListener.php +++ b/Civi/CCase/CaseChangeListener.php @@ -1,9 +1,9 @@ analyzer); + return [$this->analyzer]; } } diff --git a/Civi/CCase/Events.php b/Civi/CCase/Events.php index fcf8bf0b0044..a3890248f274 100644 --- a/Civi/CCase/Events.php +++ b/Civi/CCase/Events.php @@ -1,9 +1,9 @@ entity) { case 'Activity': if (!empty($event->object->case_id)) { - $caseId = $event->object->case_id; + $caseIds = $event->object->case_id; } break; @@ -58,7 +59,7 @@ public static function fireCaseChange(\Civi\Core\Event\PostEvent $event) { // by the time we get the post-delete event, the record is gone, so // there's nothing to analyze if ($event->action != 'delete') { - $caseId = $event->id; + $caseIds = $event->id; } break; @@ -66,30 +67,36 @@ public static function fireCaseChange(\Civi\Core\Event\PostEvent $event) { throw new \CRM_Core_Exception("CRM_Case_Listener does not support entity {$event->entity}"); } - if ($caseId) { - if (!isset(self::$isActive[$caseId])) { - $tx = new \CRM_Core_Transaction(); - \CRM_Core_Transaction::addCallback( - \CRM_Core_Transaction::PHASE_POST_COMMIT, - array(__CLASS__, 'fireCaseChangeForRealz'), - array($caseId), - "Civi_CCase_Events::fire::{$caseId}" - ); + if ($caseIds) { + foreach ((array) $caseIds as $caseId) { + if (!isset(self::$isActive[$caseId])) { + $tx = new \CRM_Core_Transaction(); + \CRM_Core_Transaction::addCallback( + \CRM_Core_Transaction::PHASE_POST_COMMIT, + [__CLASS__, 'fireCaseChangeForRealz'], + [$caseId], + "Civi_CCase_Events::fire::{$caseId}" + ); + } } } } /** - * @param $caseId + * Fire case change hook + * + * @param int|array $caseIds */ - public static function fireCaseChangeForRealz($caseId) { - if (!isset(self::$isActive[$caseId])) { - $tx = new \CRM_Core_Transaction(); - self::$isActive[$caseId] = 1; - $analyzer = new \Civi\CCase\Analyzer($caseId); - \CRM_Utils_Hook::caseChange($analyzer); - unset(self::$isActive[$caseId]); - unset($tx); + public static function fireCaseChangeForRealz($caseIds) { + foreach ((array) $caseIds as $caseId) { + if (!isset(self::$isActive[$caseId])) { + $tx = new \CRM_Core_Transaction(); + self::$isActive[$caseId] = 1; + $analyzer = new \Civi\CCase\Analyzer($caseId); + \CRM_Utils_Hook::caseChange($analyzer); + unset(self::$isActive[$caseId]); + unset($tx); + } } } diff --git a/Civi/CCase/SequenceListener.php b/Civi/CCase/SequenceListener.php index 1bceea8c8f2e..3629914bca9f 100644 --- a/Civi/CCase/SequenceListener.php +++ b/Civi/CCase/SequenceListener.php @@ -33,6 +33,10 @@ public static function onCaseChange_static(\Civi\CCase\Event\CaseChangeEvent $ev } /** + * Triggers next case activity in sequence if current activity status is updated + * to type=COMPLETED(See CRM-21598). The adjoining activity is created according + * to the sequence configured in case type. + * * @param \Civi\CCase\Event\CaseChangeEvent $event * * @throws \CiviCRM_API3_Exception @@ -47,10 +51,10 @@ public function onCaseChange(\Civi\CCase\Event\CaseChangeEvent $event) { return; } - $actTypes = array_flip(\CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'name')); - $actStatuses = array_flip(\CRM_Core_PseudoConstant::activityStatus('name')); + $actTypes = array_flip(\CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'validate')); + $actStatuses = array_flip(\CRM_Activity_BAO_Activity::getStatusesByType(\CRM_Activity_BAO_Activity::COMPLETED)); - $actIndex = $analyzer->getActivityIndex(array('activity_type_id', 'status_id')); + $actIndex = $analyzer->getActivityIndex(['activity_type_id', 'status_id']); foreach ($activitySetXML->ActivityTypes->ActivityType as $actTypeXML) { $actTypeId = $actTypes[(string) $actTypeXML->name]; @@ -59,7 +63,7 @@ public function onCaseChange(\Civi\CCase\Event\CaseChangeEvent $event) { $this->createActivity($analyzer, $actTypeXML); return; } - elseif (empty($actIndex[$actTypeId][$actStatuses['Completed']])) { + elseif (!in_array(key($actIndex[$actTypeId]), $actStatuses)) { // Haven't gotten past this step yet! return; } @@ -68,16 +72,16 @@ public function onCaseChange(\Civi\CCase\Event\CaseChangeEvent $event) { //CRM-17452 - Close the case only if all the activities are complete $activities = $analyzer->getActivities(); foreach ($activities as $activity) { - if ($activity['status_id'] != $actStatuses['Completed']) { + if (!in_array($activity['status_id'], $actStatuses)) { return; } } // OK, the all activities have completed - civicrm_api3('Case', 'create', array( + civicrm_api3('Case', 'create', [ 'id' => $analyzer->getCaseId(), 'status_id' => 'Closed', - )); + ]); $analyzer->flush(); } @@ -110,13 +114,18 @@ public function getSequenceXml($xml) { * @param \SimpleXMLElement $actXML the tag which describes the new activity */ public function createActivity(Analyzer $analyzer, \SimpleXMLElement $actXML) { - $params = array( + $params = [ 'activity_type_id' => (string) $actXML->name, 'status_id' => 'Scheduled', 'activity_date_time' => \CRM_Utils_Time::getTime('YmdHis'), 'case_id' => $analyzer->getCaseId(), - ); - $r = civicrm_api3('Activity', 'create', $params); + ]; + $case = $analyzer->getCase(); + if (!empty($case['contact_id'])) { + $params['target_id'] = \CRM_Utils_Array::first($case['contact_id']); + } + + civicrm_api3('Activity', 'create', $params); $analyzer->flush(); } diff --git a/Civi/CiUtil/Arrays.php b/Civi/CiUtil/Arrays.php index f34954c1d339..e3933ee126a2 100644 --- a/Civi/CiUtil/Arrays.php +++ b/Civi/CiUtil/Arrays.php @@ -7,6 +7,7 @@ * @package Civi\CiUtil */ class Arrays { + /** * @param $arr * @param $col @@ -14,7 +15,7 @@ class Arrays { * @return array */ public static function collect($arr, $col) { - $r = array(); + $r = []; foreach ($arr as $k => $item) { $r[$k] = $item[$col]; } diff --git a/Civi/CiUtil/CSVParser.php b/Civi/CiUtil/CSVParser.php index 05893dfda009..f48521fde75b 100644 --- a/Civi/CiUtil/CSVParser.php +++ b/Civi/CiUtil/CSVParser.php @@ -19,7 +19,7 @@ public static function parseResults($csvContent) { fwrite($fh, $csvContent); rewind($fh); - $results = array(); + $results = []; while (($r = fgetcsv($fh)) !== FALSE) { $name = str_replace('.', '::', trim($r[0])); $status = trim($r[1]); diff --git a/Civi/CiUtil/Command/AntagonistCommand.php b/Civi/CiUtil/Command/AntagonistCommand.php index 1f9e9335f669..d8d9761ffe1a 100644 --- a/Civi/CiUtil/Command/AntagonistCommand.php +++ b/Civi/CiUtil/Command/AntagonistCommand.php @@ -7,6 +7,7 @@ * @package Civi\CiUtil\Command */ class AntagonistCommand { + /** * @param $argv */ @@ -17,7 +18,7 @@ public static function main($argv) { } list ($program, $target, $suite) = $argv; - $candidateTests = \Civi\CiUtil\PHPUnitScanner::findTestsByPath(array($suite)); + $candidateTests = \Civi\CiUtil\PHPUnitScanner::findTestsByPath([$suite]); // $candidateTests = array( // array('class' => 'CRM_Core_RegionTest', 'method' => 'testBlank'), // array('class' => 'CRM_Core_RegionTest', 'method' => 'testDefault'), @@ -26,10 +27,10 @@ public static function main($argv) { // ); $antagonist = self::findAntagonist($target, $candidateTests); if ($antagonist) { - print_r(array('found an antagonist' => $antagonist)); + print_r(['found an antagonist' => $antagonist]); } else { - print_r(array('found no antagonists')); + print_r(['found no antagonists']); } } @@ -50,26 +51,26 @@ public static function main($argv) { public static function findAntagonist($target, $candidateTests) { //$phpUnit = new \Civi\CiUtil\EnvTestRunner('./scripts/phpunit', 'EnvTests'); $phpUnit = new \Civi\CiUtil\EnvTestRunner('phpunit', 'tests/phpunit/EnvTests.php'); - $expectedResults = $phpUnit->run(array($target)); - print_r(array('$expectedResults' => $expectedResults)); + $expectedResults = $phpUnit->run([$target]); + print_r(['$expectedResults' => $expectedResults]); foreach ($candidateTests as $candidateTest) { $candidateTestName = $candidateTest['class'] . '::' . $candidateTest['method']; if ($candidateTestName == $target) { continue; } - $actualResults = $phpUnit->run(array( + $actualResults = $phpUnit->run([ $candidateTestName, $target, - )); - print_r(array('$actualResults' => $actualResults)); + ]); + print_r(['$actualResults' => $actualResults]); foreach ($expectedResults as $testName => $expectedResult) { if ($actualResults[$testName] != $expectedResult) { - return array( + return [ 'antagonist' => $candidateTest, 'expectedResults' => $expectedResults, 'actualResults' => $actualResults, - ); + ]; } } } diff --git a/Civi/CiUtil/Command/CompareCommand.php b/Civi/CiUtil/Command/CompareCommand.php index ae8bd6d3f026..beae7ac77976 100644 --- a/Civi/CiUtil/Command/CompareCommand.php +++ b/Civi/CiUtil/Command/CompareCommand.php @@ -7,6 +7,7 @@ * @package Civi\CiUtil\Command */ class CompareCommand { + /** * @param $argv */ @@ -17,21 +18,22 @@ public static function main($argv) { exit(1); } - $parser = array('\Civi\CiUtil\PHPUnitParser', 'parseJsonResults'); + $parser = ['\Civi\CiUtil\PHPUnitParser', 'parseJsonResults']; $printerType = 'txt'; - $suites = array(); // array('file' => string, 'results' => array) + // array('file' => string, 'results' => array) + $suites = []; for ($i = 1; $i < count($argv); $i++) { switch ($argv[$i]) { case '--phpunit-json': - $parser = array('\Civi\CiUtil\PHPUnitParser', 'parseJsonResults'); + $parser = ['\Civi\CiUtil\PHPUnitParser', 'parseJsonResults']; break; case '--jenkins-xml': - $parser = array('\Civi\CiUtil\JenkinsParser', 'parseXmlResults'); + $parser = ['\Civi\CiUtil\JenkinsParser', 'parseXmlResults']; break; case '--csv': - $parser = array('\Civi\CiUtil\CSVParser', 'parseResults'); + $parser = ['\Civi\CiUtil\CSVParser', 'parseResults']; break; case '--out=txt': @@ -43,14 +45,15 @@ public static function main($argv) { break; default: - $suites[] = array( + $suites[] = [ 'file' => $argv[$i], 'results' => call_user_func($parser, file_get_contents($argv[$i])), - ); + ]; } } - $tests = array(); // array(string $name) + // array(string $name) + $tests = []; foreach ($suites as $suite) { $tests = array_unique(array_merge( $tests, @@ -66,7 +69,7 @@ public static function main($argv) { $printer = new \Civi\CiUtil\ComparisonPrinter(\Civi\CiUtil\Arrays::collect($suites, 'file')); } foreach ($tests as $test) { - $values = array(); + $values = []; foreach ($suites as $suite) { $values[] = isset($suite['results'][$test]) ? $suite['results'][$test] : 'MISSING'; } diff --git a/Civi/CiUtil/Command/LsCommand.php b/Civi/CiUtil/Command/LsCommand.php index 88f0d84947e3..f0db547cf54c 100644 --- a/Civi/CiUtil/Command/LsCommand.php +++ b/Civi/CiUtil/Command/LsCommand.php @@ -7,6 +7,7 @@ * @package Civi\CiUtil\Command */ class LsCommand { + /** * @param $argv */ diff --git a/Civi/CiUtil/ComparisonPrinter.php b/Civi/CiUtil/ComparisonPrinter.php index a58a83eb6704..27acd8908c6c 100644 --- a/Civi/CiUtil/ComparisonPrinter.php +++ b/Civi/CiUtil/ComparisonPrinter.php @@ -7,8 +7,8 @@ * @package Civi\CiUtil */ class ComparisonPrinter { - var $headers; - var $hasHeader = FALSE; + public $headers; + public $hasHeader = FALSE; /** * @param $headers diff --git a/Civi/CiUtil/CsvPrinter.php b/Civi/CiUtil/CsvPrinter.php index 4c4ec185a3ef..55c7dbba4393 100644 --- a/Civi/CiUtil/CsvPrinter.php +++ b/Civi/CiUtil/CsvPrinter.php @@ -7,9 +7,9 @@ * @package Civi\CiUtil */ class CsvPrinter { - var $file; - var $headers; - var $hasHeader = FALSE; + public $file; + public $headers; + public $hasHeader = FALSE; /** * @param $file diff --git a/Civi/CiUtil/JenkinsParser.php b/Civi/CiUtil/JenkinsParser.php index 8ca35b0d8488..4a1429876869 100644 --- a/Civi/CiUtil/JenkinsParser.php +++ b/Civi/CiUtil/JenkinsParser.php @@ -5,6 +5,7 @@ * Parse Jenkins result files */ class JenkinsParser { + /** * @param string $content * Xml data. @@ -13,7 +14,7 @@ class JenkinsParser { */ public static function parseXmlResults($content) { $xml = simplexml_load_string($content); - $results = array(); + $results = []; foreach ($xml->suites as $suites) { foreach ($suites->suite as $suite) { foreach ($suite->cases as $cases) { diff --git a/Civi/CiUtil/PHPUnitParser.php b/Civi/CiUtil/PHPUnitParser.php index 06e0ec02aa40..9ff2b7d572c2 100644 --- a/Civi/CiUtil/PHPUnitParser.php +++ b/Civi/CiUtil/PHPUnitParser.php @@ -5,6 +5,7 @@ * Parse phpunit result files */ class PHPUnitParser { + /** * @param string $content * Phpunit streaming JSON. @@ -13,7 +14,7 @@ class PHPUnitParser { */ protected static function parseJsonStream($content) { $content = '[' - . strtr($content, array("}{" => "},{")) + . strtr($content, ["}{" => "},{"]) . ']'; return json_decode($content, TRUE); } @@ -26,7 +27,7 @@ protected static function parseJsonStream($content) { */ public static function parseJsonResults($content) { $records = self::parseJsonStream($content); - $results = array(); + $results = []; foreach ($records as $r) { if ($r['event'] == 'test') { $results[$r['test']] = $r['status']; diff --git a/Civi/CiUtil/PHPUnitScanner.php b/Civi/CiUtil/PHPUnitScanner.php index cd7a1b9a2ecd..a1028f52f5c8 100644 --- a/Civi/CiUtil/PHPUnitScanner.php +++ b/Civi/CiUtil/PHPUnitScanner.php @@ -7,6 +7,7 @@ * Search for PHPUnit test cases */ class PHPUnitScanner { + /** * @param $path * @return array class names @@ -32,7 +33,7 @@ public static function _findTestClasses($path) { * @throws \Exception */ public static function findTestClasses($paths) { - $testClasses = array(); + $testClasses = []; $finder = new Finder(); foreach ($paths as $path) { @@ -77,17 +78,17 @@ public static function findTestClasses($paths) { * - method: string */ public static function findTestsByPath($paths) { - $r = array(); + $r = []; $testClasses = self::findTestClasses($paths); foreach ($testClasses as $testFile => $testClass) { $clazz = new \ReflectionClass($testClass); foreach ($clazz->getMethods() as $method) { if (preg_match('/^test/', $method->name)) { - $r[] = array( + $r[] = [ 'file' => $testFile, 'class' => $testClass, 'method' => $method->name, - ); + ]; } } } diff --git a/Civi/Codeception/CiviAcceptanceTesterTrait.php b/Civi/Codeception/CiviAcceptanceTesterTrait.php new file mode 100644 index 000000000000..b5a4dea57a32 --- /dev/null +++ b/Civi/Codeception/CiviAcceptanceTesterTrait.php @@ -0,0 +1,110 @@ +amOnPage($newPage); + } + + /** + * Dispatcher for login to supported plattforms + * @param $username + * CiviCRM username for the login + * @param $password + * CiviCRM password for the login + */ + public function login($username, $password) { + $config = \CRM_Core_Config::singleton(); + $handler = [$this, 'loginTo' . $config->userFramework]; + if (is_callable($handler)) { + call_user_func($handler, $username, $password); + } + else { + throw new CRM_Core_Exception("Framework {$config->userFramework} is not supported. Implement loginTo{$config->userFramework}."); + } + } + + /** + * Login to Drupal + * @param $username + * CiviCRM username for the login + * @param $password + * CiviCRM password for the login + */ + public function loginToDrupal($username, $password) { + $I = $this; + $I->amOnPage('/user'); + $I->fillField("#edit-name", $username); + $I->fillField("#edit-pass", $password); + $I->click("#edit-submit"); + $I->see("CiviCRM Home"); + } + + /** + * Login to Joomla + * @param $username + * CiviCRM username for the login + * @param $password + * CiviCRM password for the login + */ + public function loginToJoomla($username, $password) { + throw new CRM_Core_Exception("loginToJoomla is not implemented yet. Implement a corresponding login function."); + } + + /** + * Login to Wordpress + * @param $username + * CiviCRM username for the login + * @param $password + * CiviCRM password for the login + */ + public function loginToWordpress($username, $password) { + throw new CRM_Core_Exception("loginToWordpress is not implemented yet. Implement a corresponding login function."); + } + + /** + * Login to Backdrop + * @param $username + * CiviCRM username for the login + * @param $password + * CiviCRM password for the login + */ + public function loginToBackdrop($username, $password) { + throw new CRM_Core_Exception("loginToBackdrop is not implemented yet. Implement a corresponding login function."); + } + + /** + * Login as Admin User + */ + public function loginAsAdmin() { + global $_CV; + $this->login($_CV['ADMIN_USER'], $_CV['ADMIN_PASS']); + } + + /** + * Login as Demo User + */ + public function loginAsDemo() { + global $_CV; + $this->login($_CV['DEMO_USER'], $_CV['DEMO_PASS']); + } + +} diff --git a/Civi/Core/AssetBuilder.php b/Civi/Core/AssetBuilder.php new file mode 100644 index 000000000000..4f0756610ec2 --- /dev/null +++ b/Civi/Core/AssetBuilder.php @@ -0,0 +1,376 @@ +getUrl('api-fields.json'); + * + * // Define the content of `api-fields.json`. + * function hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { + * if ($asset !== 'api-fields.json') return; + * + * $entities = civicrm_api3('Entity', 'get', array()); + * $fields = array(); + * foreach ($entities['values'] as $entity) { + * $fields[$entity] = civicrm_api3($entity, 'getfields'); + * } + * + * $mimeType = 'application/json'; + * $content = json_encode($fields); + * } + * @endCode + * + * Assets can be parameterized. Each combination of ($asset,$params) + * will be cached separately. For example, we might want a copy of + * 'api-fields.json' which only includes a handful of chosen entities. + * Simply pass the chosen entities into `getUrl()`, then update + * the definition to use `$params['entities']`, as in: + * + * @code + * // Build a URL to `api-fields.json`. + * $url = \Civi::service('asset_builder')->getUrl('api-fields.json', array( + * 'entities' => array('Contact', 'Phone', 'Email', 'Address'), + * )); + * + * // Define the content of `api-fields.json`. + * function hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { + * if ($asset !== 'api-fields.json') return; + * + * $fields = array(); + * foreach ($params['entities'] as $entity) { + * $fields[$entity] = civicrm_api3($entity, 'getfields'); + * } + * + * $mimeType = 'application/json'; + * $content = json_encode($fields); + * } + * @endCode + * + * Note: These assets are designed to hold non-sensitive data, such as + * aggregated JS or common metadata. There probably are ways to + * secure it (e.g. alternative digest() calculations), but the + * current implementation is KISS. + */ +class AssetBuilder { + + /** + * @return array + * Array(string $value => string $label). + */ + public static function getCacheModes() { + return [ + '0' => ts('Disable'), + '1' => ts('Enable'), + 'auto' => ts('Auto'), + ]; + } + + /** + * @var mixed + */ + protected $cacheEnabled; + + /** + * AssetBuilder constructor. + * @param $cacheEnabled + */ + public function __construct($cacheEnabled = NULL) { + if ($cacheEnabled === NULL) { + $cacheEnabled = \Civi::settings()->get('assetCache'); + if ($cacheEnabled === 'auto') { + $cacheEnabled = !\CRM_Core_Config::singleton()->debug; + } + $cacheEnabled = (bool) $cacheEnabled; + } + $this->cacheEnabled = $cacheEnabled; + } + + /** + * Determine if $name is a well-formed asset name. + * + * @param string $name + * @return bool + */ + public function isValidName($name) { + return preg_match(';^[a-zA-Z0-9\.\-_/]+$;', $name) + && strpos($name, '..') === FALSE + && strpos($name, '.') !== FALSE; + } + + /** + * @param string $name + * Ex: 'angular.json'. + * @param array $params + * @return string + * URL. + * Ex: 'http://example.org/files/civicrm/dyn/angular.abcd1234abcd1234.json'. + */ + public function getUrl($name, $params = []) { + \CRM_Utils_Hook::getAssetUrl($name, $params); + + if (!$this->isValidName($name)) { + throw new \RuntimeException("Invalid dynamic asset name"); + } + + if ($this->isCacheEnabled()) { + $fileName = $this->build($name, $params); + return $this->getCacheUrl($fileName); + } + else { + return \CRM_Utils_System::url('civicrm/asset/builder', [ + 'an' => $name, + 'ap' => $this->encode($params), + 'ad' => $this->digest($name, $params), + ], TRUE, NULL, FALSE); + } + } + + /** + * @param string $name + * Ex: 'angular.json'. + * @param array $params + * @return string + * URL. + * Ex: '/var/www/files/civicrm/dyn/angular.abcd1234abcd1234.json'. + */ + public function getPath($name, $params = []) { + if (!$this->isValidName($name)) { + throw new \RuntimeException("Invalid dynamic asset name"); + } + + $fileName = $this->build($name, $params); + return $this->getCachePath($fileName); + } + + /** + * Build the cached copy of an $asset. + * + * @param string $name + * Ex: 'angular.json'. + * @param array $params + * @param bool $force + * Build the asset anew, even if it already exists. + * @return string + * File name (relative to cache folder). + * Ex: 'angular.abcd1234abcd1234.json'. + * @throws UnknownAssetException + */ + public function build($name, $params, $force = FALSE) { + if (!$this->isValidName($name)) { + throw new UnknownAssetException("Asset name is malformed"); + } + $nameParts = explode('.', $name); + array_splice($nameParts, -1, 0, [$this->digest($name, $params)]); + $fileName = implode('.', $nameParts); + if ($force || !file_exists($this->getCachePath($fileName))) { + // No file locking, but concurrent writers should produce + // the same data, so we'll just plow ahead. + + if (!file_exists($this->getCachePath())) { + mkdir($this->getCachePath()); + } + + $rendered = $this->render($name, $params); + file_put_contents($this->getCachePath($fileName), $rendered['content']); + return $fileName; + } + return $fileName; + } + + /** + * Generate the content for a dynamic asset. + * + * @param string $name + * @param array $params + * @return array + * Array with keys: + * - statusCode: int, ex: 200. + * - mimeType: string, ex: 'text/html'. + * - content: string, ex: 'Hello world'. + * @throws \CRM_Core_Exception + */ + public function render($name, $params = []) { + if (!$this->isValidName($name)) { + throw new UnknownAssetException("Asset name is malformed"); + } + \CRM_Utils_Hook::buildAsset($name, $params, $mimeType, $content); + if ($mimeType === NULL && $content === NULL) { + throw new UnknownAssetException("Unrecognized asset name: $name"); + } + // Beg your pardon, sir. Please may I have an HTTP response class instead? + return [ + 'statusCode' => 200, + 'mimeType' => $mimeType, + 'content' => $content, + ]; + } + + /** + * Clear out any cache files. + */ + public function clear() { + \CRM_Utils_File::cleanDir($this->getCachePath()); + } + + /** + * Determine the local path of a cache file. + * + * @param string|NULL $fileName + * Ex: 'angular.abcd1234abcd1234.json'. + * @return string + * URL. + * Ex: '/var/www/files/civicrm/dyn/angular.abcd1234abcd1234.json'. + */ + protected function getCachePath($fileName = NULL) { + // imageUploadDir has the correct functional properties but a wonky name. + $suffix = ($fileName === NULL) ? '' : (DIRECTORY_SEPARATOR . $fileName); + return \CRM_Utils_File::addTrailingSlash(\CRM_Core_Config::singleton()->imageUploadDir) + . 'dyn' . $suffix; + } + + /** + * Determine the URL of a cache file. + * + * @param string|NULL $fileName + * Ex: 'angular.abcd1234abcd1234.json'. + * @return string + * URL. + * Ex: 'http://example.org/files/civicrm/dyn/angular.abcd1234abcd1234.json'. + */ + protected function getCacheUrl($fileName = NULL) { + // imageUploadURL has the correct functional properties but a wonky name. + $suffix = ($fileName === NULL) ? '' : ('/' . $fileName); + return \CRM_Utils_File::addTrailingSlash(\CRM_Core_Config::singleton()->imageUploadURL, '/') + . 'dyn' . $suffix; + } + + /** + * Create a unique identifier for the $params. + * + * This identifier is designed to avoid accidental cache collisions. + * + * @param string $name + * @param array $params + * @return string + */ + protected function digest($name, $params) { + // WISHLIST: For secure digest, generate+persist privatekey & call hash_hmac. + ksort($params); + $digest = md5( + $name . + \CRM_Core_Resources::singleton()->getCacheCode() . + \CRM_Core_Config_Runtime::getId() . + json_encode($params) + ); + return $digest; + } + + /** + * Encode $params in a format that's optimized for shorter URLs. + * + * @param array $params + * @return string + */ + protected function encode($params) { + if (empty($params)) { + return ''; + } + + $str = json_encode($params); + if (function_exists('gzdeflate')) { + $str = gzdeflate($str); + } + return base64_encode($str); + } + + /** + * @param string $str + * @return array + */ + protected function decode($str) { + if ($str === NULL || $str === FALSE || $str === '') { + return []; + } + + $str = base64_decode($str); + if (function_exists('gzdeflate')) { + $str = gzinflate($str); + } + return json_decode($str, TRUE); + } + + /** + * @return bool + */ + public function isCacheEnabled() { + return $this->cacheEnabled; + } + + /** + * @param bool|null $cacheEnabled + * @return AssetBuilder + */ + public function setCacheEnabled($cacheEnabled) { + $this->cacheEnabled = $cacheEnabled; + return $this; + } + + /** + * (INTERNAL ONLY) + * + * Execute a page-request for `civicrm/asset/builder`. + */ + public static function pageRun() { + // Beg your pardon, sir. Please may I have an HTTP response class instead? + $asset = self::pageRender($_GET); + \CRM_Utils_System::sendResponse(new \GuzzleHttp\Psr7\Response($asset['statusCode'], ['Content-Type' => $asset['mimeType']], $asset['content'])); + } + + /** + * (INTERNAL ONLY) + * + * Execute a page-request for `civicrm/asset/builder`. + * + * @param array $get + * The _GET values. + * @return array + * Array with keys: + * - statusCode: int, ex 200. + * - mimeType: string, ex 'text/html'. + * - content: string, ex 'Hello world'. + */ + public static function pageRender($get) { + // Beg your pardon, sir. Please may I have an HTTP response class instead? + try { + $assets = \Civi::service('asset_builder'); + return $assets->render($get['an'], $assets->decode($get['ap'])); + } + catch (UnknownAssetException $e) { + return [ + 'statusCode' => 404, + 'mimeType' => 'text/plain', + 'content' => $e->getMessage(), + ]; + } + } + +} diff --git a/Civi/Core/CiviEventDispatcher.php b/Civi/Core/CiviEventDispatcher.php index a10fa63fd6f5..2334f1298ca7 100644 --- a/Civi/Core/CiviEventDispatcher.php +++ b/Civi/Core/CiviEventDispatcher.php @@ -26,7 +26,7 @@ class CiviEventDispatcher extends ContainerAwareEventDispatcher { * @var array * Array(string $eventName => trueish). */ - private $autoListeners = array(); + private $autoListeners = []; /** * Determine whether $eventName should delegate to the CMS hook system. @@ -124,10 +124,10 @@ protected function bindPatterns($eventName) { // WISHLIST: For native extensions (and possibly D6/D7/D8/BD), enumerate // the listeners and list them one-by-one. This would make it easier to // inspect via "cv debug:event-dispatcher". - $this->addListener($eventName, array( + $this->addListener($eventName, [ '\Civi\Core\CiviEventDispatcher', 'delegateToUF', - ), self::DEFAULT_HOOK_PRIORITY); + ], self::DEFAULT_HOOK_PRIORITY); } } } diff --git a/Civi/Core/CiviEventInspector.php b/Civi/Core/CiviEventInspector.php index 4f74cc54a8d9..ad260c099c32 100644 --- a/Civi/Core/CiviEventInspector.php +++ b/Civi/Core/CiviEventInspector.php @@ -33,7 +33,7 @@ class CiviEventInspector { * @see \CRM_Utils_Hook::eventDefs() */ public static function findBuiltInEvents(\Civi\Core\Event\GenericHookEvent $e) { - $skipList = array('singleton'); + $skipList = ['singleton']; $e->inspector->addStaticStubs('CRM_Utils_Hook', 'hook_civicrm_', function ($eventDef, $method) use ($skipList) { return in_array($method->name, $skipList) ? NULL : $eventDef; @@ -56,7 +56,7 @@ function ($eventDef, $method) use ($skipList) { */ public function build($force = FALSE) { if ($force || $this->eventDefs === NULL) { - $this->eventDefs = array(); + $this->eventDefs = []; \CRM_Utils_Hook::eventDefs($this); ksort($this->eventDefs); } @@ -113,7 +113,7 @@ public function validate($eventDef) { return FALSE; } - if (!in_array($eventDef['type'], array('hook', 'object'))) { + if (!in_array($eventDef['type'], ['hook', 'object'])) { return FALSE; } @@ -167,10 +167,10 @@ function ($field) { * @return CiviEventInspector */ public function addEventClass($event, $className) { - $this->add(array( + $this->add([ 'name' => $event, 'class' => $className, - )); + ]); return $this; } @@ -195,20 +195,20 @@ public function addStaticStubs($className, $prefix, $filter = NULL) { continue; } - $eventDef = array( + $eventDef = [ 'name' => $prefix . $method->name, 'description_html' => $method->getDocComment() ? \CRM_Admin_Page_APIExplorer::formatDocBlock($method->getDocComment()) : '', - 'fields' => array(), + 'fields' => [], 'class' => 'Civi\Core\Event\GenericHookEvent', 'stub' => $method, - ); + ]; foreach ($method->getParameters() as $parameter) { - $eventDef['fields'][$parameter->getName()] = array( + $eventDef['fields'][$parameter->getName()] = [ 'name' => $parameter->getName(), 'ref' => (bool) $parameter->isPassedByReference(), // WISHLIST: 'type' => 'mixed', - ); + ]; } if ($filter !== NULL) { diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 97e23379cfb8..805056456188 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -1,23 +1,13 @@ createContainer(); $containerBuilder->compile(); return $containerBuilder; } $envId = \CRM_Core_Config_Runtime::getId(); - $file = CIVICRM_TEMPLATE_COMPILEDIR . "/CachedCiviContainer.{$envId}.php"; + $file = \Civi::paths()->getPath("[civicrm.compile]/CachedCiviContainer.{$envId}.php"); $containerConfigCache = new ConfigCache($file, $cacheMode === 'auto'); if (!$containerConfigCache->isFresh()) { $containerBuilder = $this->createContainer(); $containerBuilder->compile(); $dumper = new PhpDumper($containerBuilder); $containerConfigCache->write( - $dumper->dump(array('class' => 'CachedCiviContainer')), + $dumper->dump(['class' => 'CachedCiviContainer']), $containerBuilder->getResources() ); } require_once $file; $c = new \CachedCiviContainer(); - $c->set('service_container', $c); return $c; } /** * Construct a new container. * - * @var ContainerBuilder + * @var \Symfony\Component\DependencyInjection\ContainerBuilder * @return \Symfony\Component\DependencyInjection\ContainerBuilder */ public function createContainer() { @@ -111,7 +99,7 @@ public function createContainer() { $container->setDefinition(self::SELF, new Definition( 'Civi\Core\Container', - array() + [] )); // TODO Move configuration to an external file; define caching structure @@ -132,54 +120,95 @@ public function createContainer() { $container->setDefinition('angular', new Definition( 'Civi\Angular\Manager', - array() + [] )) - ->setFactoryService(self::SELF)->setFactoryMethod('createAngularManager'); + ->setFactory([new Reference(self::SELF), 'createAngularManager']); $container->setDefinition('dispatcher', new Definition( 'Civi\Core\CiviEventDispatcher', - array(new Reference('service_container')) + [new Reference('service_container')] )) - ->setFactoryService(self::SELF)->setFactoryMethod('createEventDispatcher'); + ->setFactory([new Reference(self::SELF), 'createEventDispatcher']); $container->setDefinition('magic_function_provider', new Definition( 'Civi\API\Provider\MagicFunctionProvider', - array() + [] )); $container->setDefinition('civi_api_kernel', new Definition( 'Civi\API\Kernel', - array(new Reference('dispatcher'), new Reference('magic_function_provider')) + [new Reference('dispatcher'), new Reference('magic_function_provider')] )) - ->setFactoryService(self::SELF)->setFactoryMethod('createApiKernel'); + ->setFactory([new Reference(self::SELF), 'createApiKernel']); $container->setDefinition('cxn_reg_client', new Definition( 'Civi\Cxn\Rpc\RegistrationClient', - array() + [] )) - ->setFactoryClass('CRM_Cxn_BAO_Cxn')->setFactoryMethod('createRegistrationClient'); - - $container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', array())); - - foreach (array('js_strings', 'community_messages') as $cacheName) { - $container->setDefinition("cache.{$cacheName}", new Definition( + ->setFactory('CRM_Cxn_BAO_Cxn::createRegistrationClient'); + + $container->setDefinition('psr_log', new Definition('CRM_Core_Error_Log', [])); + + $basicCaches = [ + 'js_strings' => 'js_strings', + 'community_messages' => 'community_messages', + 'checks' => 'checks', + 'session' => 'CiviCRM Session', + 'long' => 'long', + 'groups' => 'contact groups', + 'navigation' => 'navigation', + 'customData' => 'custom data', + 'fields' => 'contact fields', + 'contactTypes' => 'contactTypes', + 'metadata' => 'metadata', + ]; + foreach ($basicCaches as $cacheSvc => $cacheGrp) { + $definitionParams = [ + 'name' => $cacheGrp, + 'type' => ['*memory*', 'SqlGroup', 'ArrayCache'], + ]; + // For Caches that we don't really care about the ttl for and/or maybe accessed + // fairly often we use the fastArrayDecorator which improves reads and writes, these + // caches should also not have concurrency risk. + $fastArrayCaches = ['groups', 'navigation', 'customData', 'fields', 'contactTypes', 'metadata']; + if (in_array($cacheSvc, $fastArrayCaches)) { + $definitionParams['withArray'] = 'fast'; + } + $container->setDefinition("cache.{$cacheSvc}", new Definition( 'CRM_Utils_Cache_Interface', - array( - array( - 'name' => $cacheName, - 'type' => array('*memory*', 'SqlGroup', 'ArrayCache'), - ), - ) - ))->setFactoryClass('CRM_Utils_Cache')->setFactoryMethod('create'); + [$definitionParams] + ))->setFactory('CRM_Utils_Cache::create'); } + // PrevNextCache cannot use memory or array cache at the moment because the + // Code in CRM_Core_BAO_PrevNextCache assumes that this cache is sql backed. + $container->setDefinition("cache.prevNextCache", new Definition( + 'CRM_Utils_Cache_Interface', + [ + [ + 'name' => 'CiviCRM Search PrevNextCache', + 'type' => ['SqlGroup'], + ], + ] + ))->setFactory('CRM_Utils_Cache::create'); + $container->setDefinition('sql_triggers', new Definition( 'Civi\Core\SqlTriggers', - array() + [] + )); + + $container->setDefinition('asset_builder', new Definition( + 'Civi\Core\AssetBuilder', + [] + )); + + $container->setDefinition('themes', new Definition( + 'Civi\Core\Themes', + [] )); $container->setDefinition('pear_mail', new Definition('Mail')) - ->setFactoryClass('CRM_Utils_Mail')->setFactoryMethod('createMailer'); + ->setFactory('CRM_Utils_Mail::createMailer'); if (empty(\Civi::$statics[__CLASS__]['boot'])) { throw new \RuntimeException("Cannot initialize container. Boot services are undefined."); @@ -189,40 +218,102 @@ public function createContainer() { } // Expose legacy singletons as services in the container. - $singletons = array( - 'resources' => 'CRM_Core_Resources', + $singletons = [ 'httpClient' => 'CRM_Utils_HttpClient', 'cache.default' => 'CRM_Utils_Cache', 'i18n' => 'CRM_Core_I18n', // Maybe? 'config' => 'CRM_Core_Config', // Maybe? 'smarty' => 'CRM_Core_Smarty', - ); + ]; foreach ($singletons as $name => $class) { $container->setDefinition($name, new Definition( $class )) - ->setFactoryClass($class)->setFactoryMethod('singleton'); + ->setFactory([$class, 'singleton']); } + $container->setAlias('cache.short', 'cache.default'); + + $container->setDefinition('resources', new Definition( + 'CRM_Core_Resources', + [new Reference('service_container')] + ))->setFactory([new Reference(self::SELF), 'createResources']); + + $container->setDefinition('prevnext', new Definition( + 'CRM_Core_PrevNextCache_Interface', + [new Reference('service_container')] + ))->setFactory([new Reference(self::SELF), 'createPrevNextCache']); + + $container->setDefinition('prevnext.driver.sql', new Definition( + 'CRM_Core_PrevNextCache_Sql', + [] + )); + + $container->setDefinition('prevnext.driver.redis', new Definition( + 'CRM_Core_PrevNextCache_Redis', + [new Reference('cache_config')] + )); + + $container->setDefinition('cache_config', new Definition('ArrayObject')) + ->setFactory([new Reference(self::SELF), 'createCacheConfig']); + + $container->setDefinition('civi.mailing.triggers', new Definition( + 'Civi\Core\SqlTrigger\TimestampTriggers', + ['civicrm_mailing', 'Mailing'] + ))->addTag('kernel.event_listener', ['event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo']); + + $container->setDefinition('civi.activity.triggers', new Definition( + 'Civi\Core\SqlTrigger\TimestampTriggers', + ['civicrm_activity', 'Activity'] + ))->addTag('kernel.event_listener', ['event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo']); + + $container->setDefinition('civi.case.triggers', new Definition( + 'Civi\Core\SqlTrigger\TimestampTriggers', + ['civicrm_case', 'Case'] + ))->addTag('kernel.event_listener', ['event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo']); + + $container->setDefinition('civi.case.staticTriggers', new Definition( + 'Civi\Core\SqlTrigger\StaticTriggers', + [ + [ + [ + 'upgrade_check' => ['table' => 'civicrm_case', 'column' => 'modified_date'], + 'table' => 'civicrm_case_activity', + 'when' => 'AFTER', + 'event' => ['INSERT'], + 'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id = NEW.case_id;\n", + ], + [ + 'upgrade_check' => ['table' => 'civicrm_case', 'column' => 'modified_date'], + 'table' => 'civicrm_activity', + 'when' => 'BEFORE', + 'event' => ['UPDATE', 'DELETE'], + 'sql' => "\nUPDATE civicrm_case SET modified_date = CURRENT_TIMESTAMP WHERE id IN (SELECT ca.case_id FROM civicrm_case_activity ca WHERE ca.activity_id = OLD.id);\n", + ], + ], + ] + )) + ->addTag('kernel.event_listener', ['event' => 'hook_civicrm_triggerInfo', 'method' => 'onTriggerInfo']); $container->setDefinition('civi_token_compat', new Definition( 'Civi\Token\TokenCompatSubscriber', - array() + [] ))->addTag('kernel.event_subscriber'); $container->setDefinition("crm_mailing_action_tokens", new Definition( "CRM_Mailing_ActionTokens", - array() + [] ))->addTag('kernel.event_subscriber'); - foreach (array('Activity', 'Contribute', 'Event', 'Mailing', 'Member') as $comp) { + foreach (['Activity', 'Contribute', 'Event', 'Mailing', 'Member'] as $comp) { $container->setDefinition("crm_" . strtolower($comp) . "_tokens", new Definition( "CRM_{$comp}_Tokens", - array() + [] ))->addTag('kernel.event_subscriber'); } if (\CRM_Utils_Constant::value('CIVICRM_FLEXMAILER_HACK_SERVICES')) { - \Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_SERVICES, array($container)); + \Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_SERVICES, [$container]); } + \CRM_Api4_Services::hook_container($container); \CRM_Utils_Hook::container($container); @@ -237,56 +328,63 @@ public function createAngularManager() { } /** - * @param ContainerInterface $container + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container * @return \Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher */ public function createEventDispatcher($container) { $dispatcher = new CiviEventDispatcher($container); - $dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\InstallationCanary', 'check')); - $dispatcher->addListener(SystemInstallEvent::EVENT_NAME, array('\Civi\Core\DatabaseInitializer', 'initialize')); - $dispatcher->addListener('hook_civicrm_pre', array('\Civi\Core\Event\PreEvent', 'dispatchSubevent'), 100); - $dispatcher->addListener('hook_civicrm_post', array('\Civi\Core\Event\PostEvent', 'dispatchSubevent'), 100); - $dispatcher->addListener('hook_civicrm_post::Activity', array('\Civi\CCase\Events', 'fireCaseChange')); - $dispatcher->addListener('hook_civicrm_post::Case', array('\Civi\CCase\Events', 'fireCaseChange')); - $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\Events', 'delegateToXmlListeners')); - $dispatcher->addListener('hook_civicrm_caseChange', array('\Civi\CCase\SequenceListener', 'onCaseChange_static')); - $dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\Core\CiviEventInspector', 'findBuiltInEvents')); + $dispatcher->addListener(SystemInstallEvent::EVENT_NAME, ['\Civi\Core\InstallationCanary', 'check']); + $dispatcher->addListener(SystemInstallEvent::EVENT_NAME, ['\Civi\Core\DatabaseInitializer', 'initialize']); + $dispatcher->addListener(SystemInstallEvent::EVENT_NAME, ['\Civi\Core\LocalizationInitializer', 'initialize']); + $dispatcher->addListener('hook_civicrm_pre', ['\Civi\Core\Event\PreEvent', 'dispatchSubevent'], 100); + $dispatcher->addListener('hook_civicrm_post', ['\Civi\Core\Event\PostEvent', 'dispatchSubevent'], 100); + $dispatcher->addListener('hook_civicrm_post::Activity', ['\Civi\CCase\Events', 'fireCaseChange']); + $dispatcher->addListener('hook_civicrm_post::Case', ['\Civi\CCase\Events', 'fireCaseChange']); + $dispatcher->addListener('hook_civicrm_caseChange', ['\Civi\CCase\Events', 'delegateToXmlListeners']); + $dispatcher->addListener('hook_civicrm_caseChange', ['\Civi\CCase\SequenceListener', 'onCaseChange_static']); + $dispatcher->addListener('hook_civicrm_eventDefs', ['\Civi\Core\CiviEventInspector', 'findBuiltInEvents']); // TODO We need a better code-convention for metadata about non-hook events. - $dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\API\Events', 'hookEventDefs')); - $dispatcher->addListener('hook_civicrm_eventDefs', array('\Civi\Core\Event\SystemInstallEvent', 'hookEventDefs')); - $dispatcher->addListener('civi.dao.postInsert', array('\CRM_Core_BAO_RecurringEntity', 'triggerInsert')); - $dispatcher->addListener('civi.dao.postUpdate', array('\CRM_Core_BAO_RecurringEntity', 'triggerUpdate')); - $dispatcher->addListener('civi.dao.postDelete', array('\CRM_Core_BAO_RecurringEntity', 'triggerDelete')); - $dispatcher->addListener('hook_civicrm_unhandled_exception', array( + $dispatcher->addListener('hook_civicrm_eventDefs', ['\Civi\API\Events', 'hookEventDefs']); + $dispatcher->addListener('hook_civicrm_eventDefs', ['\Civi\Core\Event\SystemInstallEvent', 'hookEventDefs']); + $dispatcher->addListener('hook_civicrm_buildAsset', ['\Civi\Angular\Page\Modules', 'buildAngularModules']); + $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Utils_VisualBundle', 'buildAssetJs']); + $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Utils_VisualBundle', 'buildAssetCss']); + $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Core_Resources', 'renderMenubarStylesheet']); + $dispatcher->addListener('hook_civicrm_coreResourceList', ['\CRM_Utils_System', 'appendCoreResources']); + $dispatcher->addListener('hook_civicrm_getAssetUrl', ['\CRM_Utils_System', 'alterAssetUrl']); + $dispatcher->addListener('civi.dao.postInsert', ['\CRM_Core_BAO_RecurringEntity', 'triggerInsert']); + $dispatcher->addListener('civi.dao.postUpdate', ['\CRM_Core_BAO_RecurringEntity', 'triggerUpdate']); + $dispatcher->addListener('civi.dao.postDelete', ['\CRM_Core_BAO_RecurringEntity', 'triggerDelete']); + $dispatcher->addListener('hook_civicrm_unhandled_exception', [ 'CRM_Core_LegacyErrorHandler', 'handleException', - ), -200); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Activity_ActionMapping', 'onRegisterActionMappings')); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contact_ActionMapping', 'onRegisterActionMappings')); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contribute_ActionMapping_ByPage', 'onRegisterActionMappings')); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Contribute_ActionMapping_ByType', 'onRegisterActionMappings')); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Event_ActionMapping', 'onRegisterActionMappings')); - $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, array('CRM_Member_ActionMapping', 'onRegisterActionMappings')); + ], -200); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Activity_ActionMapping', 'onRegisterActionMappings']); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Contact_ActionMapping', 'onRegisterActionMappings']); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Contribute_ActionMapping_ByPage', 'onRegisterActionMappings']); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Contribute_ActionMapping_ByType', 'onRegisterActionMappings']); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Event_ActionMapping', 'onRegisterActionMappings']); + $dispatcher->addListener(\Civi\ActionSchedule\Events::MAPPINGS, ['CRM_Member_ActionMapping', 'onRegisterActionMappings']); if (\CRM_Utils_Constant::value('CIVICRM_FLEXMAILER_HACK_LISTENERS')) { - \Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_LISTENERS, array($dispatcher)); + \Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_LISTENERS, [$dispatcher]); } return $dispatcher; } /** - * @return LockManager + * @return \Civi\Core\Lock\LockManager */ public static function createLockManager() { // Ideally, downstream implementers could override any definitions in // the container. For now, we'll make-do with some define()s. $lm = new LockManager(); $lm - ->register('/^cache\./', defined('CIVICRM_CACHE_LOCK') ? CIVICRM_CACHE_LOCK : array('CRM_Core_Lock', 'createScopedLock')) - ->register('/^data\./', defined('CIVICRM_DATA_LOCK') ? CIVICRM_DATA_LOCK : array('CRM_Core_Lock', 'createScopedLock')) - ->register('/^worker\.mailing\.send\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : array('CRM_Core_Lock', 'createCivimailLock')) - ->register('/^worker\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : array('CRM_Core_Lock', 'createScopedLock')); + ->register('/^cache\./', defined('CIVICRM_CACHE_LOCK') ? CIVICRM_CACHE_LOCK : ['CRM_Core_Lock', 'createScopedLock']) + ->register('/^data\./', defined('CIVICRM_DATA_LOCK') ? CIVICRM_DATA_LOCK : ['CRM_Core_Lock', 'createScopedLock']) + ->register('/^worker\.mailing\.send\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : ['CRM_Core_Lock', 'createCivimailLock']) + ->register('/^worker\./', defined('CIVICRM_WORK_LOCK') ? CIVICRM_WORK_LOCK : ['CRM_Core_Lock', 'createScopedLock']); // Registrations may use complex resolver expressions, but (as a micro-optimization) // the default factory is specified as an array. @@ -307,12 +405,12 @@ public function createApiKernel($dispatcher, $magicFunctionProvider) { $dispatcher->addSubscriber($magicFunctionProvider); $dispatcher->addSubscriber(new \Civi\API\Subscriber\PermissionCheck()); $dispatcher->addSubscriber(new \Civi\API\Subscriber\APIv3SchemaAdapter()); - $dispatcher->addSubscriber(new \Civi\API\Subscriber\WrapperAdapter(array( + $dispatcher->addSubscriber(new \Civi\API\Subscriber\WrapperAdapter([ \CRM_Utils_API_HTMLInputCoder::singleton(), \CRM_Utils_API_NullOutputCoder::singleton(), \CRM_Utils_API_ReloadOption::singleton(), \CRM_Utils_API_MatchOption::singleton(), - ))); + ])); $dispatcher->addSubscriber(new \Civi\API\Subscriber\XDebugSubscriber()); $kernel = new \Civi\API\Kernel($dispatcher); @@ -322,7 +420,7 @@ public function createApiKernel($dispatcher, $magicFunctionProvider) { $dispatcher->addSubscriber(new \Civi\API\Subscriber\DynamicFKAuthorization( $kernel, 'Attachment', - array('create', 'get', 'delete'), + ['create', 'get', 'delete'], // Given a file ID, determine the entity+table it's attached to. 'SELECT if(cf.id,1,0) as is_valid, cef.entity_table, cef.entity_id FROM civicrm_file cf @@ -336,17 +434,57 @@ public function createApiKernel($dispatcher, $magicFunctionProvider) { INNER JOIN civicrm_custom_group grp ON fld.custom_group_id = grp.id WHERE fld.data_type = "File" ', - array('civicrm_activity', 'civicrm_mailing', 'civicrm_contact', 'civicrm_grant') + ['civicrm_activity', 'civicrm_mailing', 'civicrm_contact', 'civicrm_grant'] )); - $kernel->setApiProviders(array( + $kernel->setApiProviders([ $reflectionProvider, $magicFunctionProvider, - )); + ]); return $kernel; } + /** + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * @return \CRM_Core_Resources + */ + public static function createResources($container) { + $sys = \CRM_Extension_System::singleton(); + return new \CRM_Core_Resources( + $sys->getMapper(), + $container->get('cache.js_strings'), + \CRM_Core_Config::isUpgradeMode() ? NULL : 'resCacheCode' + ); + } + + /** + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * @return \CRM_Core_PrevNextCache_Interface + */ + public static function createPrevNextCache($container) { + $setting = \Civi::settings()->get('prevNextBackend'); + if ($setting === 'default') { + // For initial release (5.8.x), continue defaulting to SQL. + $isTransitional = version_compare(\CRM_Utils_System::version(), '5.9.alpha1', '<'); + $cacheDriver = \CRM_Utils_Cache::getCacheDriver(); + $service = 'prevnext.driver.' . strtolower($cacheDriver); + return $container->has($service) && !$isTransitional + ? $container->get($service) + : $container->get('prevnext.driver.sql'); + } + else { + return $container->get('prevnext.driver.' . $setting); + } + } + + public static function createCacheConfig() { + $driver = \CRM_Utils_Cache::getCacheDriver(); + $settings = \CRM_Utils_Cache::getCacheSettings($driver); + $settings['driver'] = $driver; + return new \ArrayObject($settings); + } + /** * Get a list of boot services. * @@ -357,7 +495,7 @@ public function createApiKernel($dispatcher, $magicFunctionProvider) { */ public static function boot($loadFromDB) { // Array(string $serviceId => object $serviceInstance). - $bootServices = array(); + $bootServices = []; \Civi::$statics[__CLASS__]['boot'] = &$bootServices; $bootServices['runtime'] = $runtime = new \CRM_Core_Config_Runtime(); @@ -372,10 +510,10 @@ public static function boot($loadFromDB) { $userPermissionClass = 'CRM_Core_Permission_' . $runtime->userFramework; $bootServices['userPermissionClass'] = new $userPermissionClass(); - $bootServices['cache.settings'] = \CRM_Utils_Cache::create(array( + $bootServices['cache.settings'] = \CRM_Utils_Cache::create([ 'name' => 'settings', - 'type' => array('*memory*', 'SqlGroup', 'ArrayCache'), - )); + 'type' => ['*memory*', 'SqlGroup', 'ArrayCache'], + ]); $bootServices['settings_manager'] = new \Civi\Core\SettingsManager($bootServices['cache.settings']); diff --git a/Civi/Core/DAO/Event/PostDelete.php b/Civi/Core/DAO/Event/PostDelete.php index abf3df00de7a..e90732da7a4b 100644 --- a/Civi/Core/DAO/Event/PostDelete.php +++ b/Civi/Core/DAO/Event/PostDelete.php @@ -1,9 +1,9 @@ object = $object; $this->result = $result; } + } diff --git a/Civi/Core/DAO/Event/PostUpdate.php b/Civi/Core/DAO/Event/PostUpdate.php index 881f194200bd..362f026c5a65 100644 --- a/Civi/Core/DAO/Event/PostUpdate.php +++ b/Civi/Core/DAO/Event/PostUpdate.php @@ -1,9 +1,9 @@ object = $object; } + } diff --git a/Civi/Core/DAO/Event/PreDelete.php b/Civi/Core/DAO/Event/PreDelete.php index 7485c508c666..7182c30e705e 100644 --- a/Civi/Core/DAO/Event/PreDelete.php +++ b/Civi/Core/DAO/Event/PreDelete.php @@ -1,9 +1,9 @@ object = $object; } + } diff --git a/Civi/Core/DatabaseInitializer.php b/Civi/Core/DatabaseInitializer.php index 7cb31fe1d5c3..e13c8b7f89a8 100644 --- a/Civi/Core/DatabaseInitializer.php +++ b/Civi/Core/DatabaseInitializer.php @@ -1,9 +1,9 @@ 3, 'triggers' => 1, 'session' => 1, - ); + ]; civicrm_api('System', 'flush', $api_params); } diff --git a/Civi/Core/Event/GenericHookEvent.php b/Civi/Core/Event/GenericHookEvent.php index 9c47029c9b4d..c98053d20e45 100644 --- a/Civi/Core/Event/GenericHookEvent.php +++ b/Civi/Core/Event/GenericHookEvent.php @@ -1,9 +1,9 @@ action = $action; @@ -81,7 +83,7 @@ public function __construct($action, $entity, $id, &$object) { * @inheritDoc */ public function getHookValues() { - return array($this->action, $this->entity, $this->id, &$this->object); + return [$this->action, $this->entity, $this->id, &$this->object]; } } diff --git a/Civi/Core/Event/PreEvent.php b/Civi/Core/Event/PreEvent.php index af01c623c493..844175b1b133 100644 --- a/Civi/Core/Event/PreEvent.php +++ b/Civi/Core/Event/PreEvent.php @@ -1,9 +1,9 @@ action = $action; @@ -81,7 +83,7 @@ public function __construct($action, $entity, $id, &$params) { * @inheritDoc */ public function getHookValues() { - return array($this->action, $this->entity, $this->id, &$this->params); + return [$this->action, $this->entity, $this->id, &$this->params]; } } diff --git a/Civi/Core/Event/QueryEvent.php b/Civi/Core/Event/QueryEvent.php new file mode 100644 index 000000000000..d670bf2395d3 --- /dev/null +++ b/Civi/Core/Event/QueryEvent.php @@ -0,0 +1,70 @@ +query = $query; + } + + /** + * @return string|FALSE + * Ex: 'SELECT', 'INSERT', 'CREATE', 'ALTER' + * A FALSE value indicates that a singular verb could not be identified. + */ + public function getVerb() { + if ($this->verb === NULL) { + if (preg_match(';(/\*.*/\*\s*)?([a-zA-Z]+) ;', $this->query, $m)) { + $this->verb = strtolower($m[2]); + } + else { + $this->verb = FALSE; + } + } + return $this->verb; + } + +} diff --git a/Civi/Core/Event/SystemInstallEvent.php b/Civi/Core/Event/SystemInstallEvent.php index e3e8d4f001a6..eeeff18f0785 100644 --- a/Civi/Core/Event/SystemInstallEvent.php +++ b/Civi/Core/Event/SystemInstallEvent.php @@ -1,9 +1,9 @@ exception, $this->request); + return [$this->exception, $this->request]; } } diff --git a/Civi/Core/Exception/UnknownAssetException.php b/Civi/Core/Exception/UnknownAssetException.php new file mode 100644 index 000000000000..1d2359810943 --- /dev/null +++ b/Civi/Core/Exception/UnknownAssetException.php @@ -0,0 +1,6 @@ + $value) { + if (in_array($setting, $validSettings)) { + $settingsParams[$setting] = $value; + } + + } + + // ensure we don't mess with multilingual + unset($settingsParams['languageLimit']); + + // support for enabled languages (option group) + if (isset($settings['languagesOption']) && count($settings['languagesOption']) > 0) { + \CRM_Core_BAO_OptionGroup::setActiveValues('languages', $settings['languagesOption']); + } + + // set default currency in currencies_enabled (option group) + if (isset($settings['defaultCurrency'])) { + \CRM_Admin_Form_Setting_Localization::updateEnabledCurrencies([$settings['defaultCurrency']], $settings['defaultCurrency']); + } + + } + + } + + // in any case, enforce the seedLanguage as the default language + $settingsParams['lcMessages'] = $seedLanguage; + + // apply the config + civicrm_api3('Setting', 'create', $settingsParams); + + } + +} diff --git a/Civi/Core/Lock/LockInterface.php b/Civi/Core/Lock/LockInterface.php index d06ad033cdce..130f2ab9084d 100644 --- a/Civi/Core/Lock/LockInterface.php +++ b/Civi/Core/Lock/LockInterface.php @@ -1,9 +1,9 @@ getFactory($name); if ($factory) { /** @var LockInterface $lock */ - $lock = call_user_func_array($factory, array($name)); + $lock = call_user_func_array($factory, [$name]); return $lock; } else { @@ -73,7 +73,7 @@ public function create($name) { * * Categories: worker|data|cache|... * Component: core|mailing|member|contribute|... - * @param int|NULL $timeout + * @param int|null $timeout * The number of seconds to wait to get the lock. * For a default value, use NULL. * @return LockInterface @@ -111,10 +111,10 @@ public function getFactory($name) { * @see Resolver */ public function register($pattern, $factory) { - $this->rules[] = array( + $this->rules[] = [ 'pattern' => $pattern, 'factory' => $factory, - ); + ]; return $this; } diff --git a/Civi/Core/Lock/NullLock.php b/Civi/Core/Lock/NullLock.php index 4b7fb0b7ddc4..00e2a28bccc2 100644 --- a/Civi/Core/Lock/NullLock.php +++ b/Civi/Core/Lock/NullLock.php @@ -1,9 +1,9 @@ array(url => $, path => $)). */ - private $variables = array(); + private $variables = []; - private $variableFactory = array(); + private $variableFactory = []; /** * Class constructor. */ public function __construct() { + $paths = $this; $this ->register('civicrm.root', function () { return \CRM_Core_Config::singleton()->userSystem->getCiviSourceStorage(); }) + ->register('civicrm.packages', function () { + return [ + 'path' => \Civi::paths()->getPath('[civicrm.root]/packages/'), + 'url' => \Civi::paths()->getUrl('[civicrm.root]/packages/'), + ]; + }) + ->register('civicrm.vendor', function () { + return [ + 'path' => \Civi::paths()->getPath('[civicrm.root]/vendor/'), + 'url' => \Civi::paths()->getUrl('[civicrm.root]/vendor/'), + ]; + }) + ->register('civicrm.bower', function () { + return [ + 'path' => \Civi::paths()->getPath('[civicrm.root]/bower_components/'), + 'url' => \Civi::paths()->getUrl('[civicrm.root]/bower_components/'), + ]; + }) ->register('civicrm.files', function () { return \CRM_Core_Config::singleton()->userSystem->getDefaultFileStorage(); }) + ->register('civicrm.private', function () { + return [ + // For backward compatibility with existing deployments, this + // effectively returns `dirname(CIVICRM_TEMPLATE_COMPILEDIR)`. + // That's confusing. Future installers should probably set `civicrm.private` + // explicitly instead of setting `CIVICRM_TEMPLATE_COMPILEDIR`. + 'path' => \CRM_Utils_File::baseFilePath(), + ]; + }) + ->register('civicrm.log', function () { + return [ + 'path' => \Civi::paths()->getPath('[civicrm.private]/ConfigAndLog'), + ]; + }) + ->register('civicrm.compile', function () { + return [ + // These two formulations are equivalent in typical deployments; however, + // for existing systems which previously customized CIVICRM_TEMPLATE_COMPILEDIR, + // using the constant should be more backward-compatibility. + 'path' => defined('CIVICRM_TEMPLATE_COMPILEDIR') ? CIVICRM_TEMPLATE_COMPILEDIR : \Civi::paths()->getPath('[civicrm.private]/templates_c'), + ]; + }) + ->register('wp.frontend.base', function () { + return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/']; + }) + ->register('wp.frontend', function () use ($paths) { + $config = \CRM_Core_Config::singleton(); + $suffix = defined('CIVICRM_UF_WP_BASEPAGE') ? CIVICRM_UF_WP_BASEPAGE : $config->wpBasePage; + return [ + 'url' => $paths->getVariable('wp.frontend.base', 'url') . $suffix, + ]; + }) + ->register('wp.backend.base', function () { + return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/wp-admin/']; + }) + ->register('wp.backend', function () use ($paths) { + return [ + 'url' => $paths->getVariable('wp.backend.base', 'url') . 'admin.php', + ]; + }) ->register('cms', function () { - return array( + return [ 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(), 'url' => \CRM_Utils_System::baseCMSURL(), - ); + ]; }) ->register('cms.root', function () { - return array( + return [ 'path' => \CRM_Core_Config::singleton()->userSystem->cmsRootPath(), // Misleading: this *removes* the language part of the URL, producing a pristine base URL. 'url' => \CRM_Utils_System::languageNegotiationURL(\CRM_Utils_System::baseCMSURL(), FALSE, TRUE), - ); + ]; }); } @@ -78,6 +137,9 @@ public function register($name, $factory) { public function getVariable($name, $attr) { if (!isset($this->variables[$name])) { $this->variables[$name] = call_user_func($this->variableFactory[$name]); + if (isset($GLOBALS['civicrm_paths'][$name])) { + $this->variables[$name] = array_merge($this->variables[$name], $GLOBALS['civicrm_paths'][$name]); + } } if (!isset($this->variables[$name][$attr])) { throw new \RuntimeException("Cannot resolve path using \"$name.$attr\""); diff --git a/Civi/Core/Resolver.php b/Civi/Core/Resolver.php index 0d71d185e7fd..b1e54684a627 100644 --- a/Civi/Core/Resolver.php +++ b/Civi/Core/Resolver.php @@ -77,7 +77,7 @@ public function get($id) { case 'call': // Callback: Object/method in container. $obj = \Civi::service($url['host']); - return array($obj, ltrim($url['path'], '/')); + return [$obj, ltrim($url['path'], '/')]; case 'api3': // Callback: API. @@ -91,7 +91,7 @@ public function get($id) { throw new \RuntimeException("Unsupported callback scheme: " . $url['scheme']); } } - elseif (in_array($id, array('0', '1'))) { + elseif (in_array($id, ['0', '1'])) { // Callback: Constant value. return new ResolverConstantCallback((int) $id); } @@ -184,7 +184,7 @@ public function __construct($url) { * Fire an API call. */ public function __invoke() { - $apiParams = array(); + $apiParams = []; if (isset($this->url['query'])) { parse_str($this->url['query'], $apiParams); } @@ -212,7 +212,7 @@ public function __invoke() { * (e.g. "@1" => "firstValue"). */ protected function createPlaceholders($prefix, $args) { - $result = array(); + $result = []; foreach ($args as $offset => $arg) { $result[$prefix . (1 + $offset)] = $arg; } @@ -250,7 +250,8 @@ protected function interpolate(&$array, $replacements) { } class ResolverGlobalCallback { - private $mode, $path; + private $mode; + private $path; /** * Class constructor. diff --git a/Civi/Core/SettingsBag.php b/Civi/Core/SettingsBag.php index 9b415dcf433a..4155e7baf86d 100644 --- a/Civi/Core/SettingsBag.php +++ b/Civi/Core/SettingsBag.php @@ -1,9 +1,9 @@ mixed $value). */ protected $combined; @@ -83,13 +83,13 @@ class SettingsBag { /** * @param int $domainId * The domain for which we want settings. - * @param int|NULL $contactId + * @param int|null $contactId * The contact for which we want settings. Use NULL for domain settings. */ public function __construct($domainId, $contactId) { $this->domainId = $domainId; $this->contactId = $contactId; - $this->values = array(); + $this->values = []; $this->combined = NULL; } @@ -128,7 +128,7 @@ public function loadValues() { // Note: Don't use DAO child classes. They require fields() which require // translations -- which are keyed off settings! - $this->values = array(); + $this->values = []; $this->combined = NULL; // Ordinarily, we just load values from `civicrm_setting`. But upgrades require care. @@ -139,9 +139,9 @@ public function loadValues() { $isUpgradeMode = \CRM_Core_Config::isUpgradeMode(); - if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_DAO::checkFieldExists('civicrm_domain', 'config_backend', FALSE)) { + if ($isUpgradeMode && empty($this->contactId) && \CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_domain', 'config_backend', FALSE)) { $config_backend = \CRM_Core_DAO::singleValueQuery('SELECT config_backend FROM civicrm_domain WHERE id = %1', - array(1 => array($this->domainId, 'Positive'))); + [1 => [$this->domainId, 'Positive']]); $oldSettings = \CRM_Upgrade_Incremental_php_FourSeven::convertBackendToSettings($this->domainId, $config_backend); \CRM_Utils_Array::extend($this->values, $oldSettings); } @@ -180,7 +180,7 @@ public function add(array $settings) { public function all() { if ($this->combined === NULL) { $this->combined = $this->combine( - array($this->defaults, $this->values, $this->mandatory) + [$this->defaults, $this->values, $this->mandatory] ); } return $this->combined; @@ -281,16 +281,16 @@ public function set($key, $value) { protected function createQuery() { $select = \CRM_Utils_SQL_Select::from('civicrm_setting') ->select('id, name, value, domain_id, contact_id, is_domain, component_id, created_date, created_id') - ->where('domain_id = #id', array( + ->where('domain_id = #id', [ 'id' => $this->domainId, - )); + ]); if ($this->contactId === NULL) { $select->where('is_domain = 1'); } else { - $select->where('contact_id = #id', array( + $select->where('contact_id = #id', [ 'id' => $this->contactId, - )); + ]); $select->where('is_domain = 0'); } return $select; @@ -306,7 +306,7 @@ protected function createQuery() { * @return array */ protected function combine($arrays) { - $combined = array(); + $combined = []; foreach ($arrays as $array) { foreach ($array as $k => $v) { if ($v !== NULL) { @@ -326,13 +326,8 @@ protected function combine($arrays) { * The new value of the setting. */ protected function setDb($name, $value) { - if (\CRM_Core_BAO_Setting::isUpgradeFromPreFourOneAlpha1()) { - // civicrm_setting table is not going to be present. - return; - } - - $fields = array(); - $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput(array($name => $value), $fields); + $fields = []; + $fieldsToSet = \CRM_Core_BAO_Setting::validateSettingsInput([$name => $value], $fields); //We haven't traditionally validated inputs to setItem, so this breaks things. //foreach ($fieldsToSet as $settingField => &$settingValue) { // self::validateSetting($settingValue, $fields['values'][$settingField]); @@ -352,6 +347,10 @@ protected function setDb($name, $value) { } $dao->find(TRUE); + // Call 'on_change' listeners. It would be nice to only fire when there's + // a genuine change in the data. However, PHP developers have mixed + // expectations about whether 0, '0', '', NULL, and FALSE represent the same + // value, so there's no universal way to determine if a change is genuine. if (isset($metadata['on_change'])) { foreach ($metadata['on_change'] as $callback) { call_user_func( @@ -374,7 +373,7 @@ protected function setDb($name, $value) { if (!isset(\Civi::$statics[__CLASS__]['upgradeMode'])) { \Civi::$statics[__CLASS__]['upgradeMode'] = \CRM_Core_Config::isUpgradeMode(); } - if (\Civi::$statics[__CLASS__]['upgradeMode'] && \CRM_Core_DAO::checkFieldExists('civicrm_setting', 'group_name')) { + if (\Civi::$statics[__CLASS__]['upgradeMode'] && \CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_setting', 'group_name')) { $dao->group_name = 'placeholder'; } @@ -393,7 +392,6 @@ protected function setDb($name, $value) { // to save the field `group_name`, which is required in older schema. \CRM_Core_DAO::executeQuery(\CRM_Utils_SQL_Insert::dao($dao)->toSQL()); } - $dao->free(); } } diff --git a/Civi/Core/SettingsManager.php b/Civi/Core/SettingsManager.php index 2e49450d4e44..45f8b46cb7b5 100644 --- a/Civi/Core/SettingsManager.php +++ b/Civi/Core/SettingsManager.php @@ -1,9 +1,9 @@ SettingsBag $bag). */ - protected $bagsByDomain = array(), $bagsByContact = array(); + protected $bagsByDomain = []; + + + /** + * @var array + * Array (int $id => SettingsBag $bag). + */ + protected $bagsByContact = []; /** - * @var array|NULL + * @var array|null * Array(string $entity => array(string $settingName => mixed $value)). * Ex: $mandatory['domain']['uploadDir']. * NULL means "autoload from $civicrm_setting". @@ -149,7 +156,10 @@ public function useMandatory() { } /** - * @param int|NULL $domainId + * Get Settings by domain. + * + * @param int|null $domainId + * * @return SettingsBag */ public function getBagByDomain($domainId) { @@ -170,14 +180,28 @@ public function getBagByDomain($domainId) { } /** - * @param int|NULL $domainId - * @param int|NULL $contactId + * Get Settings by contact. + * + * @param int|null $domainId + * For the default domain, leave $domainID as NULL. + * @param int|null $contactId + * For the default/active user's contact, leave $domainID as NULL. + * * @return SettingsBag + * @throws \CRM_Core_Exception + * If there is no contact, then there's no SettingsBag, and we'll throw + * an exception. */ public function getBagByContact($domainId, $contactId) { if ($domainId === NULL) { $domainId = \CRM_Core_Config::domainID(); } + if ($contactId === NULL) { + $contactId = \CRM_Core_Session::getLoggedInContactID(); + if (!$contactId) { + throw new \CRM_Core_Exception("Cannot access settings subsystem - user or domain is unavailable"); + } + } $key = "$domainId:$contactId"; if (!isset($this->bagsByContact[$key])) { @@ -205,13 +229,13 @@ protected function getDefaults($entity) { return self::getSystemDefaults($entity); } - $cacheKey = 'defaults:' . $entity; + $cacheKey = 'defaults_' . $entity; $defaults = $this->cache->get($cacheKey); if (!is_array($defaults)) { - $specs = SettingsMetadata::getMetadata(array( + $specs = SettingsMetadata::getMetadata([ 'is_contact' => ($entity === 'contact' ? 1 : 0), - )); - $defaults = array(); + ]); + $defaults = []; foreach ($specs as $key => $spec) { $defaults[$key] = \CRM_Utils_Array::value('default', $spec); } @@ -255,12 +279,12 @@ protected function getMandatory($entity) { * @return array */ public static function parseMandatorySettings($civicrm_setting) { - $result = array( - 'domain' => array(), - 'contact' => array(), - ); + $result = [ + 'domain' => [], + 'contact' => [], + ]; - $rewriteGroups = array( + $rewriteGroups = [ //\CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME => 'domain', //\CRM_Core_BAO_Setting::CAMPAIGN_PREFERENCES_NAME => 'domain', //\CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME => 'domain', @@ -279,7 +303,7 @@ public static function parseMandatorySettings($civicrm_setting) { //\CRM_Core_BAO_Setting::URL_PREFERENCES_NAME => 'domain', 'domain' => 'domain', 'contact' => 'contact', - ); + ]; if (is_array($civicrm_setting)) { foreach ($civicrm_setting as $oldGroup => $values) { @@ -299,7 +323,8 @@ public function flush() { $this->mandatory = NULL; $this->cache->flush(); - \Civi::cache('settings')->flush(); // SettingsMetadata; not guaranteed to use same cache. + // SettingsMetadata; not guaranteed to use same cache. + \Civi::cache('settings')->flush(); foreach ($this->bagsByDomain as $bag) { /** @var SettingsBag $bag */ @@ -330,12 +355,12 @@ public function flush() { * @return array */ private static function getSystemDefaults($entity) { - $defaults = array(); + $defaults = []; switch ($entity) { case 'domain': - $defaults = array( + $defaults = [ 'installed' => FALSE, - 'enable_components' => array('CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge'), + 'enable_components' => ['CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge'], 'customFileUploadDir' => '[civicrm.files]/custom/', 'imageUploadDir' => '[civicrm.files]/persist/contribute/', 'uploadDir' => '[civicrm.files]/upload/', @@ -344,7 +369,7 @@ private static function getSystemDefaults($entity) { 'extensionsURL' => '[civicrm.files]/ext/', 'resourceBase' => '[civicrm.root]/', 'userFrameworkResourceURL' => '[civicrm.root]/', - ); + ]; break; } diff --git a/Civi/Core/SettingsMetadata.php b/Civi/Core/SettingsMetadata.php index c7ee46523366..5ff1bf3bf827 100644 --- a/Civi/Core/SettingsMetadata.php +++ b/Civi/Core/SettingsMetadata.php @@ -1,9 +1,9 @@ get($cacheString); - $cached = is_array($settingsMetadata); - - if (!$cached) { - $settingsMetadata = $cache->get(self::ALL); - if (empty($settingsMetadata)) { - global $civicrm_root; - $metaDataFolders = array($civicrm_root . '/settings'); - \CRM_Utils_Hook::alterSettingsFolders($metaDataFolders); - $settingsMetadata = self::loadSettingsMetaDataFolders($metaDataFolders); - $cache->set(self::ALL, $settingsMetadata); - } - } - - \CRM_Utils_Hook::alterSettingsMetaData($settingsMetadata, $domainID, NULL); - if (!$cached) { + if (!is_array($settingsMetadata)) { + global $civicrm_root; + $metaDataFolders = [$civicrm_root . '/settings']; + \CRM_Utils_Hook::alterSettingsFolders($metaDataFolders); + $settingsMetadata = self::loadSettingsMetaDataFolders($metaDataFolders); + \CRM_Utils_Hook::alterSettingsMetaData($settingsMetadata, $domainID, NULL); $cache->set($cacheString, $settingsMetadata); } self::_filterSettingsSpecification($filters, $settingsMetadata); + if ($loadOptions) { + self::loadOptions($settingsMetadata); + } return $settingsMetadata; } @@ -106,8 +99,8 @@ public static function getMetadata($filters = array(), $domainID = NULL) { * @return array */ protected static function loadSettingsMetaDataFolders($metaDataFolders) { - $settingsMetadata = array(); - $loadedFolders = array(); + $settingsMetadata = []; + $loadedFolders = []; foreach ($metaDataFolders as $metaDataFolder) { $realFolder = realpath($metaDataFolder); if (is_dir($realFolder) && !isset($loadedFolders[$realFolder])) { @@ -126,7 +119,7 @@ protected static function loadSettingsMetaDataFolders($metaDataFolders) { * @return array */ protected static function loadSettingsMetadata($metaDataFolder) { - $settingMetaData = array(); + $settingMetaData = []; $settingsFiles = \CRM_Utils_File::findFiles($metaDataFolder, '*.setting.php'); foreach ($settingsFiles as $file) { $settings = include $file; @@ -145,20 +138,41 @@ protected static function loadSettingsMetadata($metaDataFolder) { * Metadata to filter. */ protected static function _filterSettingsSpecification($filters, &$settingSpec) { - if (empty($filters)) { - return; - } - elseif (array_keys($filters) == array('name')) { - $settingSpec = array($filters['name'] => \CRM_Utils_Array::value($filters['name'], $settingSpec, '')); - return; + if (!empty($filters['name'])) { + $settingSpec = array_intersect_key($settingSpec, array_flip((array) $filters['name'])); + // FIXME: This is a workaround for settingsBag::setDb() called by unit tests with settings names that don't exist + $settingSpec += array_fill_keys((array) $filters['name'], []); + unset($filters['name']); } - else { + if (!empty($filters)) { foreach ($settingSpec as $field => $fieldValues) { if (array_intersect_assoc($fieldValues, $filters) != $filters) { unset($settingSpec[$field]); } } - return; + } + } + + /** + * Retrieve options from settings metadata + * + * @param array $settingSpec + */ + protected static function loadOptions(&$settingSpec) { + foreach ($settingSpec as &$spec) { + if (empty($spec['pseudoconstant'])) { + continue; + } + // It would be nice if we could leverage CRM_Core_PseudoConstant::get() somehow, + // but it's tightly coupled to DAO/field. However, if you really need to support + // more pseudoconstant types, then probably best to refactor it. For now, KISS. + if (!empty($spec['pseudoconstant']['callback'])) { + $spec['options'] = Resolver::singleton()->call($spec['pseudoconstant']['callback'], []); + } + elseif (!empty($spec['pseudoconstant']['optionGroupName'])) { + $keyColumn = \CRM_Utils_Array::value('keyColumn', $spec['pseudoconstant'], 'value'); + $spec['options'] = \CRM_Core_OptionGroup::values($spec['pseudoconstant']['optionGroupName'], FALSE, FALSE, TRUE, NULL, 'label', TRUE, FALSE, $keyColumn); + } } } diff --git a/Civi/Core/SettingsStack.php b/Civi/Core/SettingsStack.php new file mode 100644 index 000000000000..9183857d5cb6 --- /dev/null +++ b/Civi/Core/SettingsStack.php @@ -0,0 +1,57 @@ +stack[] = [$setting, $GLOBALS['civicrm_setting']['domain'][$setting]]; + } + else { + $this->stack[] = [$setting, NULL]; + } + $GLOBALS['civicrm_setting']['domain'][$setting] = $settingValue; + \Civi::service('settings_manager')->useMandatory(); + } + + /** + * Restore original settings. + */ + public function popAll() { + while ($frame = array_pop($this->stack)) { + list($setting, $value) = $frame; + if ($value === NULL) { + unset($GLOBALS['civicrm_setting']['domain'][$setting]); + } + else { + $GLOBALS['civicrm_setting']['domain'][$setting] = $value; + } + } + \Civi::service('settings_manager')->useMandatory(); + } + +} diff --git a/Civi/Core/SqlTrigger/StaticTriggers.php b/Civi/Core/SqlTrigger/StaticTriggers.php new file mode 100644 index 000000000000..c0f6f981b773 --- /dev/null +++ b/Civi/Core/SqlTrigger/StaticTriggers.php @@ -0,0 +1,126 @@ + 'civicrm_case', 'column'=> 'modified_date'); + * + * @see \CRM_Utils_Hook::triggerInfo + */ + private $triggers; + + /** + * StaticTriggers constructor. + * @param $triggers + */ + public function __construct($triggers) { + $this->triggers = $triggers; + } + + /** + * Add our list of triggers to the global list. + * + * @param \Civi\Core\Event\GenericHookEvent $e + * @see \CRM_Utils_Hook::triggerInfo + */ + public function onTriggerInfo($e) { + $this->alterTriggerInfo($e->info, $e->tableName); + } + + /** + * Add our list of triggers to the global list. + * + * @see \CRM_Utils_Hook::triggerInfo + * @see \CRM_Core_DAO::triggerRebuild + * + * @param array $info + * See hook_civicrm_triggerInfo. + * @param string|NULL $tableFilter + * See hook_civicrm_triggerInfo. + */ + public function alterTriggerInfo(&$info, $tableFilter = NULL) { + foreach ($this->getTriggers() as $trigger) { + if ($tableFilter !== NULL) { + // Because sadism. + if (in_array($tableFilter, (array) $trigger['table'])) { + $trigger['table'] = $tableFilter; + } + } + + if (\CRM_Core_Config::isUpgradeMode() && isset($trigger['upgrade_check'])) { + $uc = $trigger['upgrade_check']; + if (!\CRM_Core_BAO_SchemaHandler::checkIfFieldExists($uc['table'], $uc['column']) + ) { + continue; + } + } + unset($trigger['upgrade_check']); + $info[] = $trigger; + } + } + + /** + * @return mixed + */ + public function getTriggers() { + return $this->triggers; + } + + /** + * @param mixed $triggers + * @return StaticTriggers + */ + public function setTriggers($triggers) { + $this->triggers = $triggers; + return $this; + } + + /** + * @param $trigger + * @return StaticTriggers + */ + public function addTrigger($trigger) { + $this->triggers[] = $trigger; + return $this; + } + +} diff --git a/Civi/Core/SqlTrigger/TimestampTriggers.php b/Civi/Core/SqlTrigger/TimestampTriggers.php new file mode 100644 index 000000000000..c2249cf07d45 --- /dev/null +++ b/Civi/Core/SqlTrigger/TimestampTriggers.php @@ -0,0 +1,332 @@ + 'civicrm_bar', 'column' => 'foo_id'); + */ + private $relations; + + /** + * @param string $tableName + * SQL table name. + * Ex: 'civicrm_contact', 'civicrm_activity'. + * @param string $customDataEntity + * An entity name (from civicrm_custom_group.extends). + * Ex: 'Contact', 'Activity'. + * @return TimestampTriggers + */ + public static function create($tableName, $customDataEntity) { + return new static($tableName, $customDataEntity); + } + + /** + * TimestampTriggers constructor. + * + * @param string $tableName + * SQL table name. + * Ex: 'civicrm_contact', 'civicrm_activity'. + * @param string $customDataEntity + * An entity name (from civicrm_custom_group.extends). + * Ex: 'Contact', 'Activity'. + * @param string $createdDate + * SQL column name. + * Ex: 'created_date'. + * @param string $modifiedDate + * SQL column name. + * Ex: 'modified_date'. + * @param array $relations + * Ex: $relations[0] == array('table' => 'civicrm_bar', 'column' => 'foo_id'); + */ + public function __construct( + $tableName, + $customDataEntity, + $createdDate = 'created_date', + $modifiedDate = 'modified_date', + $relations = [] + ) { + $this->tableName = $tableName; + $this->customDataEntity = $customDataEntity; + $this->createdDate = $createdDate; + $this->modifiedDate = $modifiedDate; + $this->relations = $relations; + } + + /** + * Add our list of triggers to the global list. + * + * @param \Civi\Core\Event\GenericHookEvent $e + * @see \CRM_Utils_Hook::triggerInfo + */ + public function onTriggerInfo($e) { + $this->alterTriggerInfo($e->info, $e->tableName); + } + + /** + * Add our list of triggers to the global list. + * + * @see \CRM_Utils_Hook::triggerInfo + * @see \CRM_Core_DAO::triggerRebuild + * + * @param array $info + * See hook_civicrm_triggerInfo. + * @param string|NULL $tableFilter + * See hook_civicrm_triggerInfo. + */ + public function alterTriggerInfo(&$info, $tableFilter = NULL) { + // If we haven't upgraded yet, then the created_date/modified_date may not exist. + // In the past, this was a version-based check, but checkFieldExists() + // seems more robust. + if (\CRM_Core_Config::isUpgradeMode()) { + if (!\CRM_Core_BAO_SchemaHandler::checkIfFieldExists($this->getTableName(), + $this->getCreatedDate()) + ) { + return; + } + } + + if ($tableFilter == NULL || $tableFilter == $this->getTableName()) { + $info[] = [ + 'table' => [$this->getTableName()], + 'when' => 'BEFORE', + 'event' => ['INSERT'], + 'sql' => "\nSET NEW.{$this->getCreatedDate()} = CURRENT_TIMESTAMP;\n", + ]; + } + + // Update timestamp when modifying closely related tables + $relIdx = \CRM_Utils_Array::index( + ['column', 'table'], + $this->getAllRelations() + ); + foreach ($relIdx as $column => $someRelations) { + $this->generateTimestampTriggers($info, $tableFilter, + array_keys($someRelations), $column); + } + } + + /** + * Generate triggers to update the timestamp. + * + * The corresponding civicrm_FOO row is updated on insert/update/delete + * to a table that extends civicrm_FOO. + * Don't regenerate triggers for all such tables if only asked for one table. + * + * @param array $info + * Reference to the array where generated trigger information is being stored + * @param string|null $tableFilter + * Name of the table for which triggers are being generated, or NULL if all tables + * @param array $relatedTableNames + * Array of all core or all custom table names extending civicrm_FOO + * @param string $contactRefColumn + * 'contact_id' if processing core tables, 'entity_id' if processing custom tables + * + * @link https://issues.civicrm.org/jira/browse/CRM-15602 + * @see triggerInfo + */ + public function generateTimestampTriggers( + &$info, + $tableFilter, + $relatedTableNames, + $contactRefColumn + ) { + // Safety + $contactRefColumn = \CRM_Core_DAO::escapeString($contactRefColumn); + + // If specific related table requested, just process that one. + // (Reply: This feels fishy.) + if (in_array($tableFilter, $relatedTableNames)) { + $relatedTableNames = [$tableFilter]; + } + + // If no specific table requested (include all related tables), + // or a specific related table requested (as matched above) + if (empty($tableFilter) || isset($relatedTableNames[$tableFilter])) { + $info[] = [ + 'table' => $relatedTableNames, + 'when' => 'AFTER', + 'event' => ['INSERT', 'UPDATE'], + 'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = NEW.$contactRefColumn;\n", + ]; + $info[] = [ + 'table' => $relatedTableNames, + 'when' => 'AFTER', + 'event' => ['DELETE'], + 'sql' => "\nUPDATE {$this->getTableName()} SET {$this->getModifiedDate()} = CURRENT_TIMESTAMP WHERE id = OLD.$contactRefColumn;\n", + ]; + } + } + + /** + * @return string + */ + public function getTableName() { + return $this->tableName; + } + + /** + * @param string $tableName + * @return TimestampTriggers + */ + public function setTableName($tableName) { + $this->tableName = $tableName; + return $this; + } + + /** + * @return string + */ + public function getCustomDataEntity() { + return $this->customDataEntity; + } + + /** + * @param string $customDataEntity + * @return TimestampTriggers + */ + public function setCustomDataEntity($customDataEntity) { + $this->customDataEntity = $customDataEntity; + return $this; + } + + /** + * @return string + */ + public function getCreatedDate() { + return $this->createdDate; + } + + /** + * @param string $createdDate + * @return TimestampTriggers + */ + public function setCreatedDate($createdDate) { + $this->createdDate = $createdDate; + return $this; + } + + /** + * @return string + */ + public function getModifiedDate() { + return $this->modifiedDate; + } + + /** + * @param string $modifiedDate + * @return TimestampTriggers + */ + public function setModifiedDate($modifiedDate) { + $this->modifiedDate = $modifiedDate; + return $this; + } + + /** + * @return array + * Each item is an array('table' => string, 'column' => string) + */ + public function getRelations() { + return $this->relations; + } + + /** + * @param array $relations + * @return TimestampTriggers + */ + public function setRelations($relations) { + $this->relations = $relations; + return $this; + } + + /** + * Get a list of all tracked relations. + * + * This is basically the curated list (`$this->relations`) plus any custom data. + * + * @return array + * Each item is an array('table' => string, 'column' => string) + */ + public function getAllRelations() { + $relations = $this->getRelations(); + + if ($this->getCustomDataEntity()) { + $customGroupDAO = \CRM_Core_BAO_CustomGroup::getAllCustomGroupsByBaseEntity($this->getCustomDataEntity()); + $customGroupDAO->is_multiple = 0; + $customGroupDAO->find(); + while ($customGroupDAO->fetch()) { + $relations[] = [ + 'table' => $customGroupDAO->table_name, + 'column' => 'entity_id', + ]; + } + } + + return $relations; + } + +} diff --git a/Civi/Core/SqlTriggers.php b/Civi/Core/SqlTriggers.php index d4a2aab0cd56..809b2dd960e2 100644 --- a/Civi/Core/SqlTriggers.php +++ b/Civi/Core/SqlTriggers.php @@ -1,9 +1,9 @@ triggerInfo($info, $tableName, $force); @@ -82,7 +82,7 @@ public function createTriggers(&$info, $onlyTableName = NULL) { return; } - $triggers = array(); + $triggers = []; // now enumerate the tables and the events and collect the same set in a different format foreach ($info as $value) { @@ -98,14 +98,14 @@ public function createTriggers(&$info, $onlyTableName = NULL) { } if (is_string($value['table']) == TRUE) { - $tables = array($value['table']); + $tables = [$value['table']]; } else { $tables = $value['table']; } if (is_string($value['event']) == TRUE) { - $events = array(strtolower($value['event'])); + $events = [strtolower($value['event'])]; } else { $events = array_map('strtolower', $value['event']); @@ -115,12 +115,12 @@ public function createTriggers(&$info, $onlyTableName = NULL) { foreach ($tables as $tableName) { if (!isset($triggers[$tableName])) { - $triggers[$tableName] = array(); + $triggers[$tableName] = []; } foreach ($events as $eventName) { - $template_params = array('{tableName}', '{eventName}'); - $template_values = array($tableName, $eventName); + $template_params = ['{tableName}', '{eventName}']; + $template_values = [$tableName, $eventName]; $sql = str_replace($template_params, $template_values, @@ -132,17 +132,17 @@ public function createTriggers(&$info, $onlyTableName = NULL) { ); if (!isset($triggers[$tableName][$eventName])) { - $triggers[$tableName][$eventName] = array(); + $triggers[$tableName][$eventName] = []; } if (!isset($triggers[$tableName][$eventName][$whenName])) { // We're leaving out cursors, conditions, and handlers for now // they are kind of dangerous in this context anyway // better off putting them in stored procedures - $triggers[$tableName][$eventName][$whenName] = array( - 'variables' => array(), - 'sql' => array(), - ); + $triggers[$tableName][$eventName][$whenName] = [ + 'variables' => [], + 'sql' => [], + ]; } if ($variables) { @@ -181,7 +181,7 @@ public function createTriggers(&$info, $onlyTableName = NULL) { * the specific table requiring a rebuild; or NULL to rebuild all tables. */ public function dropTriggers($tableName = NULL) { - $info = array(); + $info = []; $logging = new \CRM_Logging_Schema(); $logging->triggerInfo($info, $tableName); @@ -201,17 +201,17 @@ public function dropTriggers($tableName = NULL) { * @param array $params * Optional parameters to interpolate into the string. */ - public function enqueueQuery($triggerSQL, $params = array()) { + public function enqueueQuery($triggerSQL, $params = []) { if (\Civi::settings()->get('logging_no_trigger_permission')) { if (!file_exists($this->getFile())) { // Ugh. Need to let user know somehow. This is the first change. - \CRM_Core_Session::setStatus(ts('The mysql commands you need to run are stored in %1', array( - 1 => $this->getFile(), - )), + \CRM_Core_Session::setStatus(ts('The mysql commands you need to run are stored in %1', [ + 1 => $this->getFile(), + ]), '', 'alert', - array('expires' => 0) + ['expires' => 0] ); } diff --git a/Civi/Core/Themes.php b/Civi/Core/Themes.php new file mode 100644 index 000000000000..16968eb7a382 --- /dev/null +++ b/Civi/Core/Themes.php @@ -0,0 +1,283 @@ + array $themeSpec). + */ + private $themes = NULL; + + /** + * @var \CRM_Utils_Cache_Interface + */ + private $cache = NULL; + + /** + * Theme constructor. + * @param \CRM_Utils_Cache_Interface $cache + */ + public function __construct($cache = NULL) { + $this->cache = $cache ? $cache : Civi::cache('long'); + } + + /** + * Determine the name of active theme. + * + * @return string + * Ex: "greenwich". + */ + public function getActiveThemeKey() { + if ($this->activeThemeKey === NULL) { + // Ambivalent: is it better to use $config->userFrameworkFrontend or $template->get('urlIsPublic')? + $config = \CRM_Core_Config::singleton(); + $settingKey = $config->userSystem->isFrontEndPage() ? 'theme_frontend' : 'theme_backend'; + + $themeKey = Civi::settings()->get($settingKey); + if ($themeKey === 'default') { + $themeKey = self::DEFAULT_THEME; + } + + \CRM_Utils_Hook::activeTheme($themeKey, [ + 'themes' => $this, + 'page' => \CRM_Utils_Array::value(\CRM_Core_Config::singleton()->userFrameworkURLVar, $_GET), + ]); + + $themes = $this->getAll(); + $this->activeThemeKey = isset($themes[$themeKey]) ? $themeKey : self::DEFAULT_THEME; + } + return $this->activeThemeKey; + } + + /** + * Get the definition of the theme. + * + * @param string $themeKey + * Ex: 'greenwich', 'shoreditch'. + * @return array|NULL + * @see CRM_Utils_Hook::themes + */ + public function get($themeKey) { + $all = $this->getAll(); + return isset($all[$themeKey]) ? $all[$themeKey] : NULL; + } + + /** + * Get a list of all known themes, including hidden base themes. + * + * @return array + * List of themes, keyed by name. Same format as CRM_Utils_Hook::themes(), + * but any default values are filled in. + * @see CRM_Utils_Hook::themes + */ + public function getAll() { + if ($this->themes === NULL) { + // Cache includes URLs/paths, which change with runtime. + $cacheKey = 'theme_list_' . \CRM_Core_Config_Runtime::getId(); + $this->themes = $this->cache->get($cacheKey); + if ($this->themes === NULL) { + $this->themes = $this->buildAll(); + $this->cache->set($cacheKey, $this->themes); + } + } + return $this->themes; + } + + /** + * Get a list of available themes, excluding hidden base themes. + * + * This is the same as getAll(), but abstract themes like "_fallback_" + * or "_newyork_base_" are omitted. + * + * @return array + * List of themes. + * Ex: ['greenwich' => 'Greenwich', 'shoreditch' => 'Shoreditch']. + * @see CRM_Utils_Hook::themes + */ + public function getAvailable() { + $result = array(); + foreach ($this->getAll() as $key => $theme) { + if ($key{0} !== '_') { + $result[$key] = $theme['title']; + } + } + return $result; + } + + /** + * Get the URL(s) for a themed CSS file. + * + * This implements a prioritized search, in order: + * - Check for the specified theme. + * - If that doesn't exist, check for the default theme. + * - If that doesn't exist, use the 'none' theme. + * + * @param string $active + * Active theme key. + * Ex: 'greenwich'. + * @param string $cssExt + * Ex: 'civicrm'. + * @param string $cssFile + * Ex: 'css/bootstrap.css' or 'css/civicrm.css'. + * @return array + * List of URLs to display. + * Ex: array(string $url) + */ + public function resolveUrls($active, $cssExt, $cssFile) { + $all = $this->getAll(); + if (!isset($all[$active])) { + return array(); + } + + $cssId = $this->cssId($cssExt, $cssFile); + + foreach ($all[$active]['search_order'] as $themeKey) { + if (isset($all[$themeKey]['excludes']) && in_array($cssId, $all[$themeKey]['excludes'])) { + $result = array(); + } + else { + $result = Civi\Core\Resolver::singleton() + ->call($all[$themeKey]['url_callback'], array($this, $themeKey, $cssExt, $cssFile)); + } + + if ($result !== self::PASSTHRU) { + return $result; + } + } + + throw new \RuntimeException("Failed to resolve URL. Theme metadata may be incomplete."); + } + + /** + * Construct the list of available themes. + * + * @return array + * List of themes, keyed by name. + * @see CRM_Utils_Hook::themes + */ + protected function buildAll() { + $themes = array( + 'default' => array( + 'ext' => 'civicrm', + 'title' => ts('Automatic'), + 'help' => ts('Determine a system default automatically'), + // This is an alias. url_callback, search_order don't matter. + ), + 'greenwich' => array( + 'ext' => 'civicrm', + 'title' => 'Greenwich', + 'help' => ts('CiviCRM 4.x look-and-feel'), + ), + 'none' => array( + 'ext' => 'civicrm', + 'title' => ts('None (Unstyled)'), + 'help' => ts('Disable CiviCRM\'s built-in CSS files.'), + 'search_order' => array('none', self::FALLBACK_THEME), + 'excludes' => array( + "css/civicrm.css", + "css/bootstrap.css", + ), + ), + self::FALLBACK_THEME => array( + 'ext' => 'civicrm', + 'title' => 'Fallback (Abstract Base Theme)', + 'url_callback' => '\Civi\Core\Themes\Resolvers::fallback', + 'search_order' => array(self::FALLBACK_THEME), + ), + ); + + \CRM_Utils_Hook::themes($themes); + + foreach (array_keys($themes) as $themeKey) { + $themes[$themeKey] = $this->build($themeKey, $themes[$themeKey]); + } + + return $themes; + } + + /** + * Apply defaults for a given them. + * + * @param string $themeKey + * The name of the theme. Ex: 'greenwich'. + * @param array $theme + * The original theme definition of the theme (per CRM_Utils_Hook::themes). + * @return array + * The full theme definition of the theme (per CRM_Utils_Hook::themes). + * @see CRM_Utils_Hook::themes + */ + protected function build($themeKey, $theme) { + $defaults = array( + 'name' => $themeKey, + 'url_callback' => '\Civi\Core\Themes\Resolvers::simple', + 'search_order' => array($themeKey, self::FALLBACK_THEME), + ); + $theme = array_merge($defaults, $theme); + + return $theme; + } + + /** + * @param string $cssExt + * @param string $cssFile + * @return string + */ + public function cssId($cssExt, $cssFile) { + return ($cssExt === 'civicrm') ? $cssFile : "$cssExt-$cssFile"; + } + +} diff --git a/Civi/Core/Themes/Resolvers.php b/Civi/Core/Themes/Resolvers.php new file mode 100644 index 000000000000..4671b92002e5 --- /dev/null +++ b/Civi/Core/Themes/Resolvers.php @@ -0,0 +1,97 @@ +get($themeKey); + $file = ''; + if (isset($theme['prefix'])) { + $file .= $theme['prefix']; + } + $file .= $themes->cssId($cssExt, $cssFile); + $file = $res->filterMinify($theme['ext'], $file); + + if ($res->getPath($theme['ext'], $file)) { + return array($res->getUrl($theme['ext'], $file, TRUE)); + } + else { + return Civi\Core\Themes::PASSTHRU; + } + } + + /** + * The base handler falls back to loading files from the main application (rather than + * using the theme). + * + * @param \Civi\Core\Themes $themes + * The theming subsystem. + * @param string $themeKey + * The active/desired theme key. + * @param string $cssExt + * The extension for which we want a themed CSS file (e.g. "civicrm"). + * @param string $cssFile + * File name (e.g. "css/bootstrap.css"). + * @return array|string + * List of CSS URLs, or PASSTHRU. + */ + public static function fallback($themes, $themeKey, $cssExt, $cssFile) { + $res = Civi::resources(); + return array($res->getUrl($cssExt, $cssFile, TRUE)); + } + +} diff --git a/Civi/Core/Transaction/Frame.php b/Civi/Core/Transaction/Frame.php index b147f996de0c..74ee8e83bbdb 100644 --- a/Civi/Core/Transaction/Frame.php +++ b/Civi/Core/Transaction/Frame.php @@ -1,9 +1,9 @@ commitStmt = $commitStmt; $this->rollbackStmt = $rollbackStmt; - $this->callbacks = array( - \CRM_Core_Transaction::PHASE_PRE_COMMIT => array(), - \CRM_Core_Transaction::PHASE_POST_COMMIT => array(), - \CRM_Core_Transaction::PHASE_PRE_ROLLBACK => array(), - \CRM_Core_Transaction::PHASE_POST_ROLLBACK => array(), - ); + $this->callbacks = [ + \CRM_Core_Transaction::PHASE_PRE_COMMIT => [], + \CRM_Core_Transaction::PHASE_POST_COMMIT => [], + \CRM_Core_Transaction::PHASE_PRE_ROLLBACK => [], + \CRM_Core_Transaction::PHASE_POST_ROLLBACK => [], + ]; } public function inc() { @@ -186,16 +186,16 @@ public function forceRollback() { */ public function addCallback($phase, $callback, $params = NULL, $id = NULL) { if ($id) { - $this->callbacks[$phase][$id] = array( + $this->callbacks[$phase][$id] = [ 'callback' => $callback, - 'parameters' => (is_array($params) ? $params : array($params)), - ); + 'parameters' => (is_array($params) ? $params : [$params]), + ]; } else { - $this->callbacks[$phase][] = array( + $this->callbacks[$phase][] = [ 'callback' => $callback, - 'parameters' => (is_array($params) ? $params : array($params)), - ); + 'parameters' => (is_array($params) ? $params : [$params]), + ]; } } diff --git a/Civi/Core/Transaction/Manager.php b/Civi/Core/Transaction/Manager.php index 2f4034579647..70f63ab062f1 100644 --- a/Civi/Core/Transaction/Manager.php +++ b/Civi/Core/Transaction/Manager.php @@ -1,9 +1,9 @@ stack of SQL transactions/savepoints */ - private $frames = array(); + private $frames = []; /** * @var int @@ -131,7 +131,7 @@ public function forceRollback() { // internal state of each frame is consistent with its outcome $oldFrames = $this->frames; - $this->frames = array(); + $this->frames = []; foreach ($oldFrames as $oldFrame) { $oldFrame->forceRollback(); } diff --git a/Civi/Install/Requirements.php b/Civi/Install/Requirements.php index ad0848e80bef..63b6cb05522e 100644 --- a/Civi/Install/Requirements.php +++ b/Civi/Install/Requirements.php @@ -23,14 +23,18 @@ class Requirements { */ const REQUIREMENT_ERROR = 2; - protected $system_checks = array( + /** + * @var array + */ + protected $system_checks = [ 'checkMemory', 'checkServerVariables', 'checkMysqlConnectExists', 'checkJsonEncodeExists', - ); + 'checkMultibyteExists', + ]; - protected $database_checks = array( + protected $database_checks = [ 'checkMysqlConnection', 'checkMysqlVersion', 'checkMysqlInnodb', @@ -39,7 +43,8 @@ class Requirements { 'checkMysqlTrigger', 'checkMysqlThreadStack', 'checkMysqlLockTables', - ); + 'checkMysqlUtf8mb4', + ]; /** * Run all requirements tests. @@ -68,7 +73,7 @@ public function checkAll(array $config) { * @return array */ public function checkSystem(array $file_paths) { - $errors = array(); + $errors = []; $errors[] = $this->checkFilepathIsWritable($file_paths); foreach ($this->system_checks as $check) { @@ -92,7 +97,7 @@ public function checkSystem(array $file_paths) { * @return array */ public function checkDatabase(array $db_config) { - $errors = array(); + $errors = []; foreach ($this->database_checks as $check) { $errors[] = $this->$check($db_config); @@ -104,7 +109,7 @@ public function checkDatabase(array $db_config) { /** * Generates a mysql connection * - * @param $db_confic array + * @param $db_config array * @return object mysqli connection */ protected function connect($db_config) { @@ -130,11 +135,11 @@ public function checkMemory() { $mem = $this->getPHPMemory(); $mem_string = ini_get('memory_limit'); - $results = array( + $results = [ 'title' => 'CiviCRM memory check', 'severity' => $this::REQUIREMENT_OK, 'details' => "You have $mem_string allocated (minimum 32Mb, recommended 64Mb)", - ); + ]; if ($mem < $min && $mem > 0) { $results['severity'] = $this::REQUIREMENT_ERROR; @@ -176,14 +181,14 @@ protected function getPHPMemory() { * @return array */ public function checkServerVariables() { - $results = array( + $results = [ 'title' => 'CiviCRM PHP server variables', 'severity' => $this::REQUIREMENT_OK, 'details' => 'The required $_SERVER variables are set', - ); + ]; - $required_variables = array('SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME'); - $missing = array(); + $required_variables = ['SCRIPT_NAME', 'HTTP_HOST', 'SCRIPT_FILENAME']; + $missing = []; foreach ($required_variables as $required_variable) { if (empty($_SERVER[$required_variable])) { @@ -203,11 +208,11 @@ public function checkServerVariables() { * @return array */ public function checkJsonEncodeExists() { - $results = array( + $results = [ 'title' => 'CiviCRM JSON encoding support', 'severity' => $this::REQUIREMENT_OK, 'details' => 'Function json_encode() found', - ); + ]; if (!function_exists('json_encode')) { $results['severity'] = $this::REQUIREMENT_ERROR; $results['details'] = 'Function json_encode() does not exist'; @@ -216,15 +221,33 @@ public function checkJsonEncodeExists() { return $results; } + /** + * CHeck that PHP Multibyte functions are enabled. + * @return array + */ + public function checkMultibyteExists() { + $results = [ + 'title' => 'CiviCRM MultiByte encoding support', + 'severity' => $this::REQUIREMENT_OK, + 'details' => 'PHP Multibyte etension found', + ]; + if (!function_exists('mb_substr')) { + $results['severity'] = $this::REQUIREMENT_ERROR; + $results['details'] = 'PHP Multibyte extension has not been installed and enabled'; + } + + return $results; + } + /** * @return array */ public function checkMysqlConnectExists() { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL check', 'severity' => $this::REQUIREMENT_OK, 'details' => 'Function mysqli_connect() found', - ); + ]; if (!function_exists('mysqli_connect')) { $results['severity'] = $this::REQUIREMENT_ERROR; $results['details'] = 'Function mysqli_connect() does not exist'; @@ -239,11 +262,11 @@ public function checkMysqlConnectExists() { * @return array */ public function checkMysqlConnection(array $db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL connection', 'severity' => $this::REQUIREMENT_OK, 'details' => "Connected", - ); + ]; $conn = $this->connect($db_config); @@ -269,10 +292,10 @@ public function checkMysqlConnection(array $db_config) { */ public function checkMysqlVersion(array $db_config) { $min = '5.1'; - $results = array( + $results = [ 'title' => 'CiviCRM MySQL Version', 'severity' => $this::REQUIREMENT_OK, - ); + ]; $conn = $this->connect($db_config); if (!$conn || !($info = mysqli_get_server_info($conn))) { @@ -297,11 +320,11 @@ public function checkMysqlVersion(array $db_config) { * @return array */ public function checkMysqlInnodb(array $db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM InnoDB support', 'severity' => $this::REQUIREMENT_ERROR, 'details' => 'Could not determine if MySQL has InnoDB support. Assuming none.', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -332,11 +355,11 @@ public function checkMysqlInnodb(array $db_config) { * @return array */ public function checkMysqlTempTables(array $db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL Temp Tables', 'severity' => $this::REQUIREMENT_OK, 'details' => 'MySQL server supports temporary tables', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -368,11 +391,11 @@ public function checkMysqlTempTables(array $db_config) { * @return array */ public function checkMysqlTrigger($db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL Trigger', 'severity' => $this::REQUIREMENT_OK, 'details' => 'Database supports MySQL triggers', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -413,11 +436,11 @@ public function checkMysqlTrigger($db_config) { * @return array */ public function checkMySQLAutoIncrementIncrementOne(array $db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL AutoIncrementIncrement', 'severity' => $this::REQUIREMENT_OK, 'details' => 'MySQL server auto_increment_increment is 1', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -449,11 +472,11 @@ public function checkMySQLAutoIncrementIncrementOne(array $db_config) { public function checkMysqlThreadStack($db_config) { $min_thread_stack = 192; - $results = array( + $results = [ 'title' => 'CiviCRM Mysql thread stack', 'severity' => $this::REQUIREMENT_OK, 'details' => 'MySQL thread_stack is OK', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -468,7 +491,8 @@ public function checkMysqlThreadStack($db_config) { return $results; } - $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'"); // bytes => kb + // bytes => kb + $r = mysqli_query($conn, "SHOW VARIABLES LIKE 'thread_stack'"); if (!$r) { $results['severity'] = $this::REQUIREMENT_ERROR; $results['details'] = 'Could not query thread_stack value'; @@ -490,11 +514,11 @@ public function checkMysqlThreadStack($db_config) { * @return array */ public function checkMysqlLockTables($db_config) { - $results = array( + $results = [ 'title' => 'CiviCRM MySQL Lock Tables', 'severity' => $this::REQUIREMENT_OK, 'details' => 'Can successfully lock and unlock tables', - ); + ]; $conn = $this->connect($db_config); if (!$conn) { @@ -542,13 +566,13 @@ public function checkMysqlLockTables($db_config) { * @return array */ public function checkFilepathIsWritable($file_paths) { - $results = array( + $results = [ 'title' => 'CiviCRM directories are writable', 'severity' => $this::REQUIREMENT_OK, 'details' => 'All required directories are writable: ' . implode(', ', $file_paths), - ); + ]; - $unwritable_dirs = array(); + $unwritable_dirs = []; foreach ($file_paths as $path) { if (!is_writable($path)) { $unwritable_dirs[] = $path; @@ -563,4 +587,66 @@ public function checkFilepathIsWritable($file_paths) { return $results; } + /** + * @param $db_config + * + * @return array + */ + public function checkMysqlUtf8mb4($db_config) { + $results = [ + 'title' => 'CiviCRM MySQL utf8mb4 Support', + 'severity' => $this::REQUIREMENT_OK, + 'details' => 'Your system supports the MySQL utf8mb4 character set.', + ]; + + $conn = $this->connect($db_config); + if (!$conn) { + $results['severity'] = $this::REQUIREMENT_ERROR; + $results['details'] = 'Could not connect to database'; + return $results; + } + + if (!@mysqli_select_db($conn, $db_config['database'])) { + $results['severity'] = $this::REQUIREMENT_ERROR; + $results['details'] = 'Could not select the database'; + mysqli_close($conn); + return $results; + } + + mysqli_query($conn, 'DROP TABLE IF EXISTS civicrm_utf8mb4_test'); + $r = mysqli_query($conn, 'CREATE TABLE civicrm_utf8mb4_test (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB'); + if (!$r) { + $results['severity'] = $this::REQUIREMENT_WARNING; + $results['details'] = 'It is recommended, though not yet required, to configure your MySQL server for utf8mb4 support. You will need the following MySQL server configuration: innodb_large_prefix=true innodb_file_format=barracuda innodb_file_per_table=true'; + mysqli_close($conn); + return $results; + } + mysqli_query($conn, 'DROP TABLE civicrm_utf8mb4_test'); + + // Ensure that the MySQL driver supports utf8mb4 encoding. + $version = mysqli_get_client_info(); + if (strpos($version, 'mysqlnd') !== FALSE) { + // The mysqlnd driver supports utf8mb4 starting at version 5.0.9. + $version = preg_replace('/^\D+([\d.]+).*/', '$1', $version); + if (version_compare($version, '5.0.9', '<')) { + $results['severity'] = $this::REQUIREMENT_WARNING; + $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (mysqlnd) to >= 5.0.9 for utf8mb4 support.'; + mysqli_close($conn); + return $results; + } + } + else { + // The libmysqlclient driver supports utf8mb4 starting at version 5.5.3. + if (version_compare($version, '5.5.3', '<')) { + $results['severity'] = $this::REQUIREMENT_WARNING; + $results['details'] = 'It is recommended, though not yet required, to upgrade your PHP MySQL driver (libmysqlclient) to >= 5.5.3 for utf8mb4 support.'; + mysqli_close($conn); + return $results; + } + } + + mysqli_close($conn); + return $results; + } + } diff --git a/Civi/Payment/System.php b/Civi/Payment/System.php index 1bec2566b0c9..d7b6f0e2c052 100644 --- a/Civi/Payment/System.php +++ b/Civi/Payment/System.php @@ -16,7 +16,7 @@ class System { /** * @var array cache */ - private $cache = array(); + private $cache = []; /** * @return \Civi\Payment\System @@ -38,7 +38,7 @@ public static function singleton() { * Override the config check. This is required in uninstall as no valid instances exist * but will deliberately not work with any valid processors. * - * @return CRM_Core_Payment|NULL + * @return \CRM_Core_Payment|NULL * * @throws \CRM_Core_Exception */ @@ -53,18 +53,17 @@ public function getByProcessor($processor, $force = FALSE) { } else { $paymentClass = 'CRM_Core_' . $processor['class_name']; - if (empty($paymentClass)) { + if (empty($processor['class_name'])) { throw new \CRM_Core_Exception('no class provided'); } - require_once str_replace('_', DIRECTORY_SEPARATOR, $paymentClass) . '.php'; } - $processorObject = new $paymentClass(!empty($processor['is_test']) ? 'test' : 'live', $processor); - if (!$force && $processorObject->checkConfig()) { - $processorObject = NULL; - } - else { - $processorObject->setPaymentProcessor($processor); + $processorObject = NULL; + if (class_exists($paymentClass)) { + $processorObject = new $paymentClass(!empty($processor['is_test']) ? 'test' : 'live', $processor); + if ($force || !$processorObject->checkConfig()) { + $processorObject->setPaymentProcessor($processor); + } } $this->cache[$id] = $processorObject; } @@ -72,19 +71,48 @@ public function getByProcessor($processor, $force = FALSE) { return $this->cache[$id]; } + /** + * Execute checkConfig() on the payment processor Object. + * This function creates a new instance of the processor object and returns the output of checkConfig + * + * @param array $processor + * + * @return string|NULL + * + * @throws \CRM_Core_Exception + */ + public function checkProcessorConfig($processor) { + $ext = \CRM_Extension_System::singleton()->getMapper(); + if ($ext->isExtensionKey($processor['class_name'])) { + $paymentClass = $ext->keyToClass($processor['class_name'], 'payment'); + require_once $ext->classToPath($paymentClass); + } + else { + $paymentClass = 'CRM_Core_' . $processor['class_name']; + if (empty($paymentClass)) { + throw new \CRM_Core_Exception('no class provided'); + } + require_once str_replace('_', DIRECTORY_SEPARATOR, $paymentClass) . '.php'; + } + + $processorObject = new $paymentClass(!empty($processor['is_test']) ? 'test' : 'live', $processor); + return $processorObject->checkConfig(); + } + /** * Get payment processor by it's ID. * * @param int $id * * @return \CRM_Core_Payment|NULL + * * @throws \CiviCRM_API3_Exception */ public function getById($id) { if ($id == 0) { return new \CRM_Core_Payment_Manual(); } - $processor = civicrm_api3('payment_processor', 'getsingle', array('id' => $id, 'is_test' => NULL)); + $processor = civicrm_api3('payment_processor', 'getsingle', ['id' => $id, 'is_test' => NULL]); return self::getByProcessor($processor); } @@ -96,7 +124,7 @@ public function getById($id) { * @throws \CiviCRM_API3_Exception */ public function getByName($name, $is_test) { - $processor = civicrm_api3('payment_processor', 'getsingle', array('name' => $name, 'is_test' => $is_test)); + $processor = civicrm_api3('payment_processor', 'getsingle', ['name' => $name, 'is_test' => $is_test]); return self::getByProcessor($processor); } @@ -106,7 +134,12 @@ public function getByName($name, $is_test) { * This is particularly used for tests. */ public function flushProcessors() { - $this->cache = array(); + $this->cache = []; + if (isset(\Civi::$statics['CRM_Contribute_BAO_ContributionRecur'])) { + unset(\Civi::$statics['CRM_Contribute_BAO_ContributionRecur']); + } + \CRM_Core_PseudoConstant::flush('paymentProcessor'); + civicrm_api3('PaymentProcessor', 'getfields', ['cache_clear' => 1]); \CRM_Financial_BAO_PaymentProcessor::getAllPaymentProcessors('all', TRUE); \CRM_Financial_BAO_PaymentProcessor::getAllPaymentProcessors('live', TRUE); \CRM_Financial_BAO_PaymentProcessor::getAllPaymentProcessors('test', TRUE); @@ -124,11 +157,11 @@ public function flushProcessors() { * @throws \CiviCRM_API3_Exception */ public function getByClass($className) { - return $this->getByProcessor(array( + return $this->getByProcessor([ 'class_name' => $className, 'id' => 0, 'is_test' => 0, - ), + ], TRUE); } diff --git a/Civi/Test.php b/Civi/Test.php index e70f0f40262f..51d929054dab 100644 --- a/Civi/Test.php +++ b/Civi/Test.php @@ -14,7 +14,7 @@ class Test { /** * @var array */ - private static $singletons = array(); + private static $singletons = []; /** * Get the data source used for testing. @@ -45,7 +45,7 @@ public static function dsn($part = NULL) { /** * Get a connection to the test database. * - * @return PDO + * @return \PDO */ public static function pdo() { if (!isset(self::$singletons['pdo'])) { @@ -55,7 +55,7 @@ public static function pdo() { try { self::$singletons['pdo'] = new PDO("mysql:host={$host}" . ($port ? ";port=$port" : ""), $dsninfo['username'], $dsninfo['password'], - array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE) + [PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE] ); } catch (PDOException $e) { @@ -127,7 +127,6 @@ public static function schema() { return self::$singletons['schema']; } - /** * @return \Civi\Test\Data */ diff --git a/Civi/Test/Api3DocTrait.php b/Civi/Test/Api3DocTrait.php new file mode 100644 index 000000000000..27440f70fa79 --- /dev/null +++ b/Civi/Test/Api3DocTrait.php @@ -0,0 +1,211 @@ +_apiversion; + $result = $this->callAPISuccess($entity, $action, $params); + $this->documentMe($entity, $action, $params, $result, $function, $file, $description, $exampleName); + return $result; + } + + /** + * Create test generated example in api/v3/examples. + * + * To turn this off (e.g. on the server) set + * define(DONT_DOCUMENT_TEST_CONFIG ,1); + * in your settings file + * + * @param string $entity + * @param string $action + * @param array $params + * Array as passed to civicrm_api function. + * @param array $result + * Array as received from the civicrm_api function. + * @param string $testFunction + * Calling function - generally __FUNCTION__. + * @param string $testFile + * Called from file - generally __FILE__. + * @param string $description + * Descriptive text for the example file. + * @param string $exampleName + * Name for this example file (CamelCase) - if omitted the action name will be substituted. + */ + private function documentMe($entity, $action, $params, $result, $testFunction, $testFile, $description = "", $exampleName = NULL) { + if ($params['version'] != 3 || (defined('DONT_DOCUMENT_TEST_CONFIG') && DONT_DOCUMENT_TEST_CONFIG)) { + return; + } + $entity = _civicrm_api_get_camel_name($entity); + $action = strtolower($action); + + if (empty($exampleName)) { + // Attempt to convert lowercase action name to CamelCase. + // This is clunky/imperfect due to the convention of all lowercase actions. + $exampleName = \CRM_Utils_String::convertStringToCamel($action); + $knownPrefixes = [ + 'Get', + 'Set', + 'Create', + 'Update', + 'Send', + ]; + foreach ($knownPrefixes as $prefix) { + if (strpos($exampleName, $prefix) === 0 && $prefix != $exampleName) { + $exampleName[strlen($prefix)] = strtoupper($exampleName[strlen($prefix)]); + } + } + } + + $this->tidyExampleResult($result); + if (isset($params['version'])) { + unset($params['version']); + } + // Format multiline description as array + $desc = []; + if (is_string($description) && strlen($description)) { + foreach (explode("\n", $description) as $line) { + $desc[] = trim($line); + } + } + $smarty = \CRM_Core_Smarty::singleton(); + $smarty->assign('testFunction', $testFunction); + $smarty->assign('function', _civicrm_api_get_entity_name_from_camel($entity) . "_$action"); + foreach ($params as $index => $param) { + if (is_string($param)) { + $params[$index] = addslashes($param); + } + } + $smarty->assign('params', $params); + $smarty->assign('entity', $entity); + $smarty->assign('testFile', basename($testFile)); + $smarty->assign('description', $desc); + $smarty->assign('result', $result); + $smarty->assign('action', $action); + + global $civicrm_root; + if (file_exists($civicrm_root . '/tests/templates/documentFunction.tpl')) { + if (!is_dir($civicrm_root . "/api/v3/examples/$entity")) { + mkdir($civicrm_root . "/api/v3/examples/$entity"); + } + $f = fopen($civicrm_root . "/api/v3/examples/$entity/$exampleName.ex.php", "w+b"); + $contents = $smarty->fetch($civicrm_root . '/tests/templates/documentFunction.tpl'); + $contents = \CRM_Core_CodeGen_Util_ArraySyntaxConverter::convert($contents); + fwrite($f, $contents); + fclose($f); + } + } + + /** + * Tidy up examples array so that fields that change often ..don't + * and debug related fields are unset + * + * @param array $result + */ + public function tidyExampleResult(&$result) { + if (!is_array($result)) { + return; + } + $fieldsToChange = [ + 'hash' => '67eac7789eaee00', + 'modified_date' => '2012-11-14 16:02:35', + 'created_date' => '2013-07-28 08:49:19', + 'create_date' => '20120130621222105', + 'application_received_date' => '20130728084957', + 'in_date' => '2013-07-28 08:50:19', + 'scheduled_date' => '20130728085413', + 'approval_date' => '20130728085413', + 'pledge_start_date_high' => '20130726090416', + 'start_date' => '2013-07-29 00:00:00', + 'event_start_date' => '2013-07-29 00:00:00', + 'end_date' => '2013-08-04 00:00:00', + 'event_end_date' => '2013-08-04 00:00:00', + 'decision_date' => '20130805000000', + ]; + + $keysToUnset = ['xdebug', 'undefined_fields']; + foreach ($keysToUnset as $unwantedKey) { + if (isset($result[$unwantedKey])) { + unset($result[$unwantedKey]); + } + } + if (isset($result['values'])) { + if (!is_array($result['values'])) { + return; + } + $resultArray = &$result['values']; + } + elseif (is_array($result)) { + $resultArray = &$result; + } + else { + return; + } + + foreach ($resultArray as $index => &$values) { + if (!is_array($values)) { + continue; + } + foreach ($values as $key => &$value) { + if (substr($key, 0, 3) == 'api' && is_array($value)) { + if (isset($value['is_error'])) { + // we have a std nested result format + $this->tidyExampleResult($value); + } + else { + foreach ($value as &$nestedResult) { + // this is an alternative syntax for nested results a keyed array of results + $this->tidyExampleResult($nestedResult); + } + } + } + if (in_array($key, $keysToUnset)) { + unset($values[$key]); + break; + } + if (array_key_exists($key, $fieldsToChange) && !empty($value)) { + $value = $fieldsToChange[$key]; + } + if (is_string($value)) { + $value = addslashes($value); + } + } + } + } + +} diff --git a/Civi/Test/Api3TestTrait.php b/Civi/Test/Api3TestTrait.php new file mode 100644 index 000000000000..bc8b0b9125d9 --- /dev/null +++ b/Civi/Test/Api3TestTrait.php @@ -0,0 +1,676 @@ + 1 + * else provide full message + * @param array $result + * @param $expected + * @param array $valuesToExclude + * @param string $prefix + * Extra test to add to message. + */ + public function assertAPIArrayComparison($result, $expected, $valuesToExclude = [], $prefix = '') { + $valuesToExclude = array_merge($valuesToExclude, ['debug', 'xdebug', 'sequential']); + foreach ($valuesToExclude as $value) { + if (isset($result[$value])) { + unset($result[$value]); + } + if (isset($expected[$value])) { + unset($expected[$value]); + } + } + $this->assertEquals($result, $expected, "api result array comparison failed " . $prefix . print_r($result, TRUE) . ' was compared to ' . print_r($expected, TRUE)); + } + + /** + * Check that a deleted item has been deleted. + * + * @param $entity + * @param $id + */ + public function assertAPIDeleted($entity, $id) { + $this->callAPISuccess($entity, 'getcount', ['id' => $id], 0); + } + + /** + * Check that api returned 'is_error' => 1. + * + * @param array $apiResult + * Api result. + * @param string $prefix + * Extra test to add to message. + * @param null $expectedError + */ + public function assertAPIFailure($apiResult, $prefix = '', $expectedError = NULL) { + if (!empty($prefix)) { + $prefix .= ': '; + } + if ($expectedError && !empty($apiResult['is_error'])) { + $this->assertContains($expectedError, $apiResult['error_message'], 'api error message not as expected' . $prefix); + } + $this->assertEquals(1, $apiResult['is_error'], "api call should have failed but it succeeded " . $prefix . (print_r($apiResult, TRUE))); + $this->assertNotEmpty($apiResult['error_message']); + } + + /** + * Check that api returned 'is_error' => 0. + * + * @param array $apiResult + * Api result. + * @param string $prefix + * Extra test to add to message. + */ + public function assertAPISuccess($apiResult, $prefix = '') { + if (!empty($prefix)) { + $prefix .= ': '; + } + $errorMessage = empty($apiResult['error_message']) ? '' : " " . $apiResult['error_message']; + + if (!empty($apiResult['debug_information'])) { + $errorMessage .= "\n " . print_r($apiResult['debug_information'], TRUE); + } + if (!empty($apiResult['trace'])) { + $errorMessage .= "\n" . print_r($apiResult['trace'], TRUE); + } + $this->assertEmpty(\CRM_Utils_Array::value('is_error', $apiResult), $prefix . $errorMessage); + } + + /** + * This function exists to wrap api functions. + * so we can ensure they fail where expected & throw exceptions without litterering the test with checks + * @param string $entity + * @param string $action + * @param array $params + * @param string $expectedErrorMessage + * Error. + * @param null $extraOutput + * @return array|int + */ + public function callAPIFailure($entity, $action, $params, $expectedErrorMessage = NULL, $extraOutput = NULL) { + if (is_array($params)) { + $params += [ + 'version' => $this->_apiversion, + ]; + } + $result = $this->civicrm_api($entity, $action, $params); + $this->assertAPIFailure($result, "We expected a failure for $entity $action but got a success", $expectedErrorMessage); + return $result; + } + + /** + * wrap api functions. + * so we can ensure they succeed & throw exceptions without litterering the test with checks + * + * @param string $entity + * @param string $action + * @param array $params + * @param mixed $checkAgainst + * Optional value to check result against, implemented for getvalue,. + * getcount, getsingle. Note that for getvalue the type is checked rather than the value + * for getsingle the array is compared against an array passed in - the id is not compared (for + * better or worse ) + * + * @return array|int + * + * @throws \CRM_Core_Exception + */ + public function callAPISuccess($entity, $action, $params = [], $checkAgainst = NULL) { + $params = array_merge([ + 'version' => $this->_apiversion, + 'debug' => 1, + ], + $params + ); + switch (strtolower($action)) { + case 'getvalue': + return $this->callAPISuccessGetValue($entity, $params, $checkAgainst); + + case 'getsingle': + return $this->callAPISuccessGetSingle($entity, $params, $checkAgainst); + + case 'getcount': + return $this->callAPISuccessGetCount($entity, $params, $checkAgainst); + } + $result = $this->civicrm_api($entity, $action, $params); + $this->assertAPISuccess($result, "Failure in api call for $entity $action"); + return $result; + } + + /** + * This function exists to wrap api getValue function & check the result + * so we can ensure they succeed & throw exceptions without litterering the test with checks + * There is a type check in this + * + * @param string $entity + * @param array $params + * @param int $count + * + * @throws \CRM_Core_Exception + * + * @return array|int + */ + public function callAPISuccessGetCount($entity, $params, $count = NULL) { + $params += [ + 'version' => $this->_apiversion, + 'debug' => 1, + ]; + $result = $this->civicrm_api($entity, 'getcount', $params); + if (!is_int($result) || !empty($result['is_error']) || isset($result['values'])) { + throw new \CRM_Core_Exception('Invalid getcount result : ' . print_r($result, TRUE) . " type :" . gettype($result)); + } + if (is_int($count)) { + $this->assertEquals($count, $result, "incorrect count returned from $entity getcount"); + } + return $result; + } + + /** + * This function exists to wrap api getsingle function & check the result + * so we can ensure they succeed & throw exceptions without litterering the test with checks + * + * @param string $entity + * @param array $params + * @param array $checkAgainst + * Array to compare result against. + * - boolean + * - integer + * - double + * - string + * - array + * - object + * + * @throws \CRM_Core_Exception + * + * @return array|int + */ + public function callAPISuccessGetSingle($entity, $params, $checkAgainst = NULL) { + $params += [ + 'version' => $this->_apiversion, + ]; + $result = $this->civicrm_api($entity, 'getsingle', $params); + if (!is_array($result) || !empty($result['is_error']) || isset($result['values'])) { + $unfilteredResult = $this->civicrm_api($entity, 'get', ['version' => $this->_apiversion]); + throw new \CRM_Core_Exception( + 'Invalid getsingle result' . print_r($result, TRUE) + . "\n entity: $entity . \n params \n " . print_r($params, TRUE) + . "\n entities retrieved with blank params \n" . print_r($unfilteredResult, TRUE) + ); + } + if ($checkAgainst) { + // @todo - have gone with the fn that unsets id? should we check id? + $this->checkArrayEquals($result, $checkAgainst); + } + return $result; + } + + /** + * This function exists to wrap api getValue function & check the result + * so we can ensure they succeed & throw exceptions without litterering the test with checks + * There is a type check in this + * + * @param string $entity + * @param array $params + * @param string $type + * Per http://php.net/manual/en/function.gettype.php possible types. + * - boolean + * - integer + * - double + * - string + * - array + * - object + * + * @return array|int + * @throws \CRM_Core_Exception + */ + public function callAPISuccessGetValue($entity, $params, $type = NULL) { + $params += [ + 'version' => $this->_apiversion, + 'debug' => 1, + ]; + $result = $this->civicrm_api($entity, 'getvalue', $params); + if (is_array($result) && (!empty($result['is_error']) || isset($result['values']))) { + throw new \CRM_Core_Exception('Invalid getvalue result' . print_r($result, TRUE)); + } + if ($type) { + if ($type === 'integer') { + // api seems to return integers as strings + $this->assertTrue(is_numeric($result), "expected a numeric value but got " . print_r($result, 1)); + } + else { + $this->assertType($type, $result, "returned result should have been of type $type but was "); + } + } + return $result; + } + + /** + * A stub for the API interface. This can be overriden by subclasses to change how the API is called. + * + * @param $entity + * @param $action + * @param array $params + * @return array|int + */ + public function civicrm_api($entity, $action, $params = []) { + if (\CRM_Utils_Array::value('version', $params) == 4) { + return $this->runApi4Legacy($entity, $action, $params); + } + return civicrm_api($entity, $action, $params); + } + + /** + * Emulate v3 syntax so we can run api3 tests on v4 + * + * @param $v3Entity + * @param $v3Action + * @param array $v3Params + * @return array|int + * @throws \API_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Exception + */ + public function runApi4Legacy($v3Entity, $v3Action, $v3Params = []) { + $v4Entity = self::convertEntityNameToApi4($v3Entity); + $v4Action = $v3Action = strtolower($v3Action); + $v4Params = ['checkPermissions' => isset($v3Params['check_permissions']) ? (bool) $v3Params['check_permissions'] : FALSE]; + $sequential = !empty($v3Params['sequential']); + $options = \_civicrm_api3_get_options_from_params($v3Params, in_array($v4Entity, ['Contact', 'Participant', 'Event', 'Group', 'Contribution', 'Membership'])); + $indexBy = in_array($v3Action, ['get', 'create', 'replace']) && !$sequential ? 'id' : NULL; + $onlyId = !empty($v3Params['format.only_id']); + $onlySuccess = !empty($v3Params['format.is_success']); + if (!empty($v3Params['filters']['is_current']) || !empty($v3Params['isCurrent'])) { + $v4Params['current'] = TRUE; + } + $language = !empty($v3Params['options']['language']) ? $v3Params['options']['language'] : \CRM_Utils_Array::value('option.language', $v3Params); + if ($language) { + $v4Params['language'] = $language; + } + $toRemove = ['option.', 'return', 'api.', 'format.']; + $chains = []; + $custom = []; + foreach ($v3Params as $key => $val) { + foreach ($toRemove as $remove) { + if (strpos($key, $remove) === 0) { + if ($remove == 'api.') { + $chains[$key] = $val; + } + unset($v3Params[$key]); + } + } + } + + $v3Fields = civicrm_api3($v3Entity, 'getfields', ['action' => $v3Action])['values']; + + // Fix 'null' + foreach ($v3Params as $key => $val) { + if ($val === 'null') { + $v3Params[$key] = NULL; + } + } + + if ($v4Entity == 'Setting') { + $indexBy = NULL; + $v4Params['domainId'] = \CRM_Utils_Array::value('domain_id', $v3Params); + if ($v3Action == 'getfields') { + if (!empty($v3Params['name'])) { + $v3Params['filters']['name'] = $v3Params['name']; + } + foreach (\CRM_Utils_Array::value('filters', $v3Params, []) as $filter => $val) { + $v4Params['where'][] = [$filter, '=', $val]; + } + } + if ($v3Action == 'create') { + $v4Action = 'set'; + } + if ($v3Action == 'revert') { + $v4Params['select'] = (array) $v3Params['name']; + } + if ($v3Action == 'getvalue') { + $options['return'] = [$v3Params['name'] => 1]; + $v3Params = []; + } + \CRM_Utils_Array::remove($v3Params, 'domain_id', 'name'); + } + + \CRM_Utils_Array::remove($v3Params, 'options', 'debug', 'version', 'sort', 'offset', 'rowCount', 'check_permissions', 'sequential', 'filters', 'isCurrent'); + + // Work around ugly hack in v3 Domain api + if ($v4Entity == 'Domain') { + $v3Fields['version'] = ['name' => 'version', 'api.aliases' => ['domain_version']]; + unset($v3Fields['domain_version']); + } + + foreach ($v3Fields as $name => $field) { + // Resolve v3 aliases + foreach (\CRM_Utils_Array::value('api.aliases', $field, []) as $alias) { + if (isset($v3Params[$alias])) { + $v3Params[$field['name']] = $v3Params[$alias]; + unset($v3Params[$alias]); + } + } + // Convert custom field names + if (strpos($name, 'custom_') === 0 && is_numeric($name[7])) { + // Strictly speaking, using titles instead of names is incorrect, but it works for + // unit tests where names and titles are identical and saves an extra db lookup. + $custom[$field['groupTitle']][$field['title']] = $name; + $v4FieldName = $field['groupTitle'] . '.' . $field['title']; + if (isset($v3Params[$name])) { + $v3Params[$v4FieldName] = $v3Params[$name]; + unset($v3Params[$name]); + } + if (isset($options['return'][$name])) { + $options['return'][$v4FieldName] = 1; + unset($options['return'][$name]); + } + } + } + + switch ($v3Action) { + case 'getcount': + $v4Params['select'] = ['row_count']; + // No break - keep processing as get + case 'getsingle': + case 'getvalue': + $v4Action = 'get'; + // No break - keep processing as get + case 'get': + if ($options['return'] && $v3Action !== 'getcount') { + $v4Params['select'] = array_keys($options['return']); + } + if ($options['limit'] && $v4Entity != 'Setting') { + $v4Params['limit'] = $options['limit']; + } + if ($options['offset']) { + $v4Params['offset'] = $options['offset']; + } + if ($options['sort']) { + foreach (explode(',', $options['sort']) as $sort) { + list($sortField, $sortDir) = array_pad(explode(' ', trim($sort)), 2, 'ASC'); + $v4Params['orderBy'][$sortField] = $sortDir; + } + } + break; + + case 'replace': + if (empty($v3Params['values'])) { + $v4Action = 'delete'; + } + else { + $v4Params['records'] = $v3Params['values']; + } + unset($v3Params['values']); + break; + + case 'create': + case 'update': + if (!empty($v3Params['id'])) { + $v4Action = 'update'; + $v4Params['where'][] = ['id', '=', $v3Params['id']]; + } + + $v4Params['values'] = $v3Params; + unset($v4Params['values']['id']); + break; + + case 'delete': + if (!empty($v3Params['id'])) { + $v4Params['where'][] = ['id', '=', $v3Params['id']]; + } + break; + + case 'getoptions': + $indexBy = 0; + $v4Action = 'getFields'; + $v4Params += [ + 'where' => [['name', '=', $v3Params['field']]], + 'loadOptions' => TRUE, + ]; + break; + + case 'getfields': + $v4Action = 'getFields'; + if (!empty($v3Params['action']) || !empty($v3Params['api_action'])) { + $v4Params['action'] = !empty($v3Params['action']) ? $v3Params['action'] : $v3Params['api_action']; + } + $indexBy = !$sequential ? 'name' : NULL; + break; + } + + // Ensure this api4 entity/action exists + try { + $actionInfo = \civicrm_api4($v4Entity, 'getActions', ['checkPermissions' => FALSE, 'where' => [['name', '=', $v4Action]]]); + } + catch (NotImplementedException $e) { + // For now we'll mark the test incomplete if a v4 entity doesn't exit yet + $this->markTestIncomplete($e->getMessage()); + } + if (!isset($actionInfo[0])) { + throw new \Exception("Api4 $v4Entity $v4Action does not exist."); + } + + // Migrate special params like fix_address + foreach ($actionInfo[0]['params'] as $v4ParamName => $paramInfo) { + // camelCase in api4, lower_case in api3 + $v3ParamName = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $v4ParamName)); + if (isset($v3Params[$v3ParamName])) { + $v4Params[$v4ParamName] = $v3Params[$v3ParamName]; + unset($v3Params[$v3ParamName]); + if ($paramInfo['type'][0] == 'bool') { + $v4Params[$v4ParamName] = (bool) $v4Params[$v4ParamName]; + } + } + } + + // Build where clause for 'getcount', 'getsingle', 'getvalue', 'get' & 'replace' + if ($v4Action == 'get' || $v3Action == 'replace') { + foreach ($v3Params as $key => $val) { + $op = '='; + if (is_array($val) && count($val) == 1 && array_intersect_key($val, array_flip(\CRM_Core_DAO::acceptedSQLOperators()))) { + foreach ($val as $op => $newVal) { + $val = $newVal; + } + } + $v4Params['where'][] = [$key, $op, $val]; + } + } + + try { + $result = \civicrm_api4($v4Entity, $v4Action, $v4Params, $indexBy); + } + catch (\Exception $e) { + return $onlySuccess ? 0 : [ + 'is_error' => 1, + 'error_message' => $e->getMessage(), + 'version' => 4, + ]; + } + + if (($v3Action == 'getsingle' || $v3Action == 'getvalue') && count($result) != 1) { + return $onlySuccess ? 0 : [ + 'is_error' => 1, + 'error_message' => "Expected one $v4Entity but found " . count($result), + 'count' => count($result), + ]; + } + + if ($onlySuccess) { + return 1; + } + + if ($v3Action == 'getcount') { + return $result->count(); + } + + if ($onlyId) { + return $result->first()['id']; + } + + if ($v3Action == 'getvalue' && $v4Entity == 'Setting') { + return \CRM_Utils_Array::value('value', $result->first()); + } + + if ($v3Action == 'getvalue') { + return \CRM_Utils_Array::value(array_keys($options['return'])[0], $result->first()); + } + + // Mimic api3 behavior when using 'replace' action to delete all + if ($v3Action == 'replace' && $v4Action == 'delete') { + $result->exchangeArray([]); + } + + if ($v3Action == 'getoptions') { + return [ + 'is_error' => 0, + 'count' => $result['options'] ? count($result['options']) : 0, + 'values' => $result['options'] ?: [], + 'version' => 4, + ]; + } + + // Emulate the weird return format of api3 settings + if (($v3Action == 'get' || $v3Action == 'create') && $v4Entity == 'Setting') { + $settings = []; + foreach ($result as $item) { + $settings[$item['domain_id']][$item['name']] = $item['value']; + } + $result->exchangeArray($sequential ? array_values($settings) : $settings); + } + + foreach ($result as $index => $row) { + // Run chains + foreach ($chains as $key => $params) { + $result[$index][$key] = $this->runApi4LegacyChain($key, $params, $v4Entity, $row, $sequential); + } + // Resolve custom field names + foreach ($custom as $group => $fields) { + foreach ($fields as $field => $v3FieldName) { + if (isset($row["$group.$field"])) { + $result[$index][$v3FieldName] = $row["$group.$field"]; + unset($result[$index]["$group.$field"]); + } + } + } + } + + if ($v3Action == 'getsingle') { + return $result->first(); + } + + return [ + 'is_error' => 0, + 'version' => 4, + 'count' => count($result), + 'values' => (array) $result, + 'id' => is_object($result) && count($result) == 1 ? \CRM_Utils_Array::value('id', $result->first()) : NULL, + ]; + } + + /** + * @param string $key + * @param mixed $params + * @param string $mainEntity + * @param array $result + * @param bool $sequential + * @return array + * @throws \API_Exception + */ + protected function runApi4LegacyChain($key, $params, $mainEntity, $result, $sequential) { + // Handle an array of multiple calls using recursion + if (is_array($params) && isset($params[0]) && is_array($params[0])) { + $results = []; + foreach ($params as $chain) { + $results[] = $this->runApi4LegacyChain($key, $chain, $mainEntity, $result, $sequential); + } + return $results; + } + + // Handle single api call + list(, $chainEntity, $chainAction) = explode('.', $key); + $lcChainEntity = \_civicrm_api_get_entity_name_from_camel($chainEntity); + $chainEntity = self::convertEntityNameToApi4($chainEntity); + $lcMainEntity = \_civicrm_api_get_entity_name_from_camel($mainEntity); + $params = is_array($params) ? $params : []; + + // Api3 expects this to be inherited + $params += ['sequential' => $sequential]; + + // Replace $value.field_name + foreach ($params as $name => $param) { + if (is_string($param) && strpos($param, '$value.') === 0) { + $param = substr($param, 7); + $params[$name] = \CRM_Utils_Array::value($param, $result); + } + } + + try { + $getFields = civicrm_api4($chainEntity, 'getFields', ['select' => ['name']], 'name'); + } + catch (NotImplementedException $e) { + $this->markTestIncomplete($e->getMessage()); + } + + // Emulate the string-fu guesswork that api3 does + if ($chainEntity == $mainEntity && empty($params['id']) && !empty($result['id'])) { + $params['id'] = $result['id']; + } + elseif (empty($params['id']) && !empty($result[$lcChainEntity . '_id'])) { + $params['id'] = $result[$lcChainEntity . '_id']; + } + elseif (!empty($result['id']) && isset($getFields[$lcMainEntity . '_id']) && empty($params[$lcMainEntity . '_id'])) { + $params[$lcMainEntity . '_id'] = $result['id']; + } + return $this->runApi4Legacy($chainEntity, $chainAction, $params); + } + + /** + * Fix the naming differences between api3 & api4 entities. + * + * @param string $legacyName + * @return string + */ + public static function convertEntityNameToApi4($legacyName) { + $api4Name = \CRM_Utils_String::convertStringToCamel($legacyName); + $map = [ + 'Im' => 'IM', + 'Acl' => 'ACL', + ]; + return \CRM_Utils_Array::value($api4Name, $map, $api4Name); + } + +} diff --git a/Civi/Test/CiviEnvBuilder.php b/Civi/Test/CiviEnvBuilder.php index 003e14545dbb..767d34033a81 100644 --- a/Civi/Test/CiviEnvBuilder.php +++ b/Civi/Test/CiviEnvBuilder.php @@ -19,10 +19,10 @@ class CiviEnvBuilder { protected $name; - private $steps = array(); + private $steps = []; /** - * @var string|NULL + * @var string|null * A digest of the values in $steps. */ private $targetSignature = NULL; diff --git a/Civi/Test/CiviEnvBuilder/CallbackStep.php b/Civi/Test/CiviEnvBuilder/CallbackStep.php index 6d0c9f055c3a..8e6110bee38e 100644 --- a/Civi/Test/CiviEnvBuilder/CallbackStep.php +++ b/Civi/Test/CiviEnvBuilder/CallbackStep.php @@ -1,5 +1,6 @@ action, array('install', 'uninstall'))) { + if (!in_array($this->action, ['install', 'uninstall'])) { return FALSE; } foreach ($this->names as $name) { diff --git a/Civi/Test/CiviEnvBuilder/SqlFileStep.php b/Civi/Test/CiviEnvBuilder/SqlFileStep.php index c901931721a2..ffb9ca1b0d46 100644 --- a/Civi/Test/CiviEnvBuilder/SqlFileStep.php +++ b/Civi/Test/CiviEnvBuilder/SqlFileStep.php @@ -12,13 +12,12 @@ public function __construct($file) { $this->file = $file; } - public function getSig() { - return implode(' ', array( + return implode(' ', [ $this->file, filemtime($this->file), filectime($this->file), - )); + ]); } public function isValid() { diff --git a/Civi/Test/CiviEnvBuilder/SqlStep.php b/Civi/Test/CiviEnvBuilder/SqlStep.php index 7a2736b019c8..e892883e03ce 100644 --- a/Civi/Test/CiviEnvBuilder/SqlStep.php +++ b/Civi/Test/CiviEnvBuilder/SqlStep.php @@ -1,5 +1,6 @@ sql = $sql; } - public function getSig() { return md5($this->sql); } diff --git a/Civi/Test/CiviEnvBuilder/StepInterface.php b/Civi/Test/CiviEnvBuilder/StepInterface.php index 3d6dc95cc1e3..8ed2c2ce77df 100644 --- a/Civi/Test/CiviEnvBuilder/StepInterface.php +++ b/Civi/Test/CiviEnvBuilder/StepInterface.php @@ -2,6 +2,7 @@ namespace Civi\Test\CiviEnvBuilder; interface StepInterface { + public function getSig(); public function isValid(); diff --git a/Civi/Test/CiviTestListener.php b/Civi/Test/CiviTestListener.php index d923fad9c988..35a8a0c7281c 100644 --- a/Civi/Test/CiviTestListener.php +++ b/Civi/Test/CiviTestListener.php @@ -2,289 +2,302 @@ namespace Civi\Test; -/** - * Class CiviTestListener - * @package Civi\Test - * - * CiviTestListener participates in test-execution, looking for test-classes - * which have certain tags. If the tags are found, the listener will perform - * additional setup/teardown logic. - * - * @see EndToEndInterface - * @see HeadlessInterface - * @see HookInterface - */ -class CiviTestListener extends \PHPUnit_Framework_BaseTestListener { - /** - * @var \CRM_Core_TemporaryErrorScope - */ - private $errorScope; - - /** - * @var array - * Ex: $cache['Some_Test_Class']['civicrm_foobar'] = 'hook_civicrm_foobar'; - * Array(string $testClass => Array(string $hookName => string $methodName)). - */ - private $cache = array(); +if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { + class_alias('Civi\Test\Legacy\CiviTestListener', 'Civi\Test\CiviTestListener'); + // Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class + // gets defined without executing the code before it and so the definition is not properly conditional) +} +else { /** - * @var \CRM_Core_Transaction|NULL + * Class CiviTestListener + * @package Civi\Test + * + * CiviTestListener participates in test-execution, looking for test-classes + * which have certain tags. If the tags are found, the listener will perform + * additional setup/teardown logic. + * + * @see EndToEndInterface + * @see HeadlessInterface + * @see HookInterface */ - private $tx; + class CiviTestListener extends \PHPUnit\Framework\BaseTestListener { + /** + * @var \CRM_Core_TemporaryErrorScope + */ + private $errorScope; - public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { - $byInterface = $this->indexTestsByInterface($suite->tests()); - $this->validateGroups($byInterface); - $this->autoboot($byInterface); - } + /** + * @var array + * Ex: $cache['Some_Test_Class']['civicrm_foobar'] = 'hook_civicrm_foobar'; + * Array(string $testClass => Array(string $hookName => string $methodName)). + */ + private $cache = []; - public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) { - $this->cache = array(); - } + /** + * @var \CRM_Core_Transaction|NULL + */ + private $tx; - public function startTest(\PHPUnit_Framework_Test $test) { - if ($this->isCiviTest($test)) { - error_reporting(E_ALL); - $this->errorScope = \CRM_Core_TemporaryErrorScope::useException(); + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $byInterface = $this->indexTestsByInterface($suite->tests()); + $this->validateGroups($byInterface); + $this->autoboot($byInterface); } - if ($test instanceof HeadlessInterface) { - $this->bootHeadless($test); + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $this->cache = []; } - if ($test instanceof HookInterface) { - // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config - // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. - $this->registerHooks($test); - } + public function startTest(\PHPUnit\Framework\Test $test) { + if ($this->isCiviTest($test)) { + error_reporting(E_ALL); + $this->errorScope = \CRM_Core_TemporaryErrorScope::useException(); + } - if ($test instanceof TransactionalInterface) { - $this->tx = new \CRM_Core_Transaction(TRUE); - $this->tx->rollback(); - } - else { - $this->tx = NULL; - } - } + if ($test instanceof HeadlessInterface) { + $this->bootHeadless($test); + } - public function endTest(\PHPUnit_Framework_Test $test, $time) { - if ($test instanceof TransactionalInterface) { - $this->tx->rollback()->commit(); - $this->tx = NULL; - } - if ($test instanceof HookInterface) { - \CRM_Utils_Hook::singleton()->reset(); - } - if ($this->isCiviTest($test)) { - error_reporting(E_ALL & ~E_NOTICE); - $this->errorScope = NULL; + if ($test instanceof HookInterface) { + // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config + // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. + $this->registerHooks($test); + } + + if ($test instanceof TransactionalInterface) { + $this->tx = new \CRM_Core_Transaction(TRUE); + $this->tx->rollback(); + } + else { + $this->tx = NULL; + } } - } - /** - * @param HeadlessInterface|\PHPUnit_Framework_Test $test - */ - protected function bootHeadless($test) { - if (CIVICRM_UF !== 'UnitTests') { - throw new \RuntimeException('HeadlessInterface requires CIVICRM_UF=UnitTests'); + public function endTest(\PHPUnit\Framework\Test $test, $time) { + if ($test instanceof TransactionalInterface) { + $this->tx->rollback()->commit(); + $this->tx = NULL; + } + if ($test instanceof HookInterface) { + \CRM_Utils_Hook::singleton()->reset(); + } + if ($this->isCiviTest($test)) { + error_reporting(E_ALL & ~E_NOTICE); + $this->errorScope = NULL; + } } - // Hrm, this seems wrong. Shouldn't we be resetting the entire session? - $session = \CRM_Core_Session::singleton(); - $session->set('userID', NULL); + /** + * @param HeadlessInterface|\PHPUnit\Framework\Test $test + */ + protected function bootHeadless($test) { + if (CIVICRM_UF !== 'UnitTests') { + throw new \RuntimeException('HeadlessInterface requires CIVICRM_UF=UnitTests'); + } + + // Hrm, this seems wrong. Shouldn't we be resetting the entire session? + $session = \CRM_Core_Session::singleton(); + $session->set('userID', NULL); - $test->setUpHeadless(); + $test->setUpHeadless(); - \CRM_Utils_System::flushCache(); - \Civi::reset(); - \CRM_Core_Session::singleton()->set('userID', NULL); - $config = \CRM_Core_Config::singleton(TRUE, TRUE); // ugh, performance + \CRM_Utils_System::flushCache(); + \Civi::reset(); + \CRM_Core_Session::singleton()->set('userID', NULL); + // ugh, performance + $config = \CRM_Core_Config::singleton(TRUE, TRUE); - if (property_exists($config->userPermissionClass, 'permissions')) { - $config->userPermissionClass->permissions = NULL; + if (property_exists($config->userPermissionClass, 'permissions')) { + $config->userPermissionClass->permissions = NULL; + } } - } - /** - * @param \Civi\Test\HookInterface $test - * @return array - * Array(string $hookName => string $methodName)). - */ - protected function findTestHooks(HookInterface $test) { - $class = get_class($test); - if (!isset($this->cache[$class])) { - $funcs = array(); - foreach (get_class_methods($class) as $func) { - if (preg_match('/^hook_/', $func)) { - $funcs[substr($func, 5)] = $func; + /** + * @param \Civi\Test\HookInterface $test + * @return array + * Array(string $hookName => string $methodName)). + */ + protected function findTestHooks(HookInterface $test) { + $class = get_class($test); + if (!isset($this->cache[$class])) { + $funcs = []; + foreach (get_class_methods($class) as $func) { + if (preg_match('/^hook_/', $func)) { + $funcs[substr($func, 5)] = $func; + } } + $this->cache[$class] = $funcs; } - $this->cache[$class] = $funcs; + return $this->cache[$class]; } - return $this->cache[$class]; - } - - /** - * @param \PHPUnit_Framework_Test $test - * @return bool - */ - protected function isCiviTest(\PHPUnit_Framework_Test $test) { - return $test instanceof HookInterface || $test instanceof HeadlessInterface; - } - /** - * Find any hook functions in $test and register them. - * - * @param \Civi\Test\HookInterface $test - */ - protected function registerHooks(HookInterface $test) { - if (CIVICRM_UF !== 'UnitTests') { - // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. - // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. - throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); + /** + * @param \PHPUnit\Framework\Test $test + * @return bool + */ + protected function isCiviTest(\PHPUnit\Framework\Test $test) { + return $test instanceof HookInterface || $test instanceof HeadlessInterface; } - \CRM_Utils_Hook::singleton()->reset(); - /** @var \CRM_Utils_Hook_UnitTests $hooks */ - $hooks = \CRM_Utils_Hook::singleton(); - foreach ($this->findTestHooks($test) as $hook => $func) { - $hooks->setHook($hook, array($test, $func)); - } - } - /** - * The first time we come across HeadlessInterface or EndToEndInterface, we'll - * try to autoboot. - * - * Once the system is booted, there's nothing we can do -- we're stuck with that - * environment. (Thank you, prolific define()s!) If there's a conflict between a - * test-class and the active boot-level, then we'll have to bail. - * - * @param array $byInterface - * List of test classes, keyed by major interface (HeadlessInterface vs EndToEndInterface). - */ - protected function autoboot($byInterface) { - if (defined('CIVICRM_UF')) { - // OK, nothing we can do. System has booted already. + /** + * Find any hook functions in $test and register them. + * + * @param \Civi\Test\HookInterface $test + */ + protected function registerHooks(HookInterface $test) { + if (CIVICRM_UF !== 'UnitTests') { + // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. + // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. + throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); + } + \CRM_Utils_Hook::singleton()->reset(); + /** @var \CRM_Utils_Hook_UnitTests $hooks */ + $hooks = \CRM_Utils_Hook::singleton(); + foreach ($this->findTestHooks($test) as $hook => $func) { + $hooks->setHook($hook, [$test, $func]); + } } - elseif (!empty($byInterface['HeadlessInterface'])) { - putenv('CIVICRM_UF=UnitTests'); + + /** + * The first time we come across HeadlessInterface or EndToEndInterface, we'll + * try to autoboot. + * + * Once the system is booted, there's nothing we can do -- we're stuck with that + * environment. (Thank you, prolific define()s!) If there's a conflict between a + * test-class and the active boot-level, then we'll have to bail. + * + * @param array $byInterface + * List of test classes, keyed by major interface (HeadlessInterface vs EndToEndInterface). + */ + protected function autoboot($byInterface) { + if (defined('CIVICRM_UF')) { + // OK, nothing we can do. System has booted already. + } + elseif (!empty($byInterface['HeadlessInterface'])) { + putenv('CIVICRM_UF=UnitTests'); + // phpcs:disable eval($this->cv('php:boot --level=full', 'phpcode')); - } - elseif (!empty($byInterface['EndToEndInterface'])) { - putenv('CIVICRM_UF='); + // phpcs:enable + } + elseif (!empty($byInterface['EndToEndInterface'])) { + putenv('CIVICRM_UF='); + // phpcs:disable eval($this->cv('php:boot --level=full', 'phpcode')); - } - - $blurb = "Tip: Run the headless tests and end-to-end tests separately, e.g.\n" - . " $ phpunit4 --group headless\n" - . " $ phpunit4 --group e2e \n"; + // phpcs:enable + } - if (!empty($byInterface['HeadlessInterface']) && CIVICRM_UF !== 'UnitTests') { - $testNames = implode(', ', array_keys($byInterface['HeadlessInterface'])); - throw new \RuntimeException("Suite includes headless tests ($testNames) which require CIVICRM_UF=UnitTests.\n\n$blurb"); - } - if (!empty($byInterface['EndToEndInterface']) && CIVICRM_UF === 'UnitTests') { - $testNames = implode(', ', array_keys($byInterface['EndToEndInterface'])); - throw new \RuntimeException("Suite includes end-to-end tests ($testNames) which do not support CIVICRM_UF=UnitTests.\n\n$blurb"); - } - } + $blurb = "Tip: Run the headless tests and end-to-end tests separately, e.g.\n" + . " $ phpunit5 --group headless\n" + . " $ phpunit5 --group e2e \n"; - /** - * Call the "cv" command. - * - * This duplicates the standalone `cv()` wrapper that is recommended in bootstrap.php. - * This duplication is necessary because `cv()` is optional, and downstream implementers - * may alter, rename, or omit the wrapper, and (by virtue of its role in bootstrap) there - * it is impossible to define it centrally. - * - * @param string $cmd - * The rest of the command to send. - * @param string $decode - * Ex: 'json' or 'phpcode'. - * @return string - * Response output (if the command executed normally). - * @throws \RuntimeException - * If the command terminates abnormally. - */ - protected function cv($cmd, $decode = 'json') { - $cmd = 'cv ' . $cmd; - $descriptorSpec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => STDERR); - $oldOutput = getenv('CV_OUTPUT'); - putenv("CV_OUTPUT=json"); - $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); - putenv("CV_OUTPUT=$oldOutput"); - fclose($pipes[0]); - $result = stream_get_contents($pipes[1]); - fclose($pipes[1]); - if (proc_close($process) !== 0) { - throw new \RuntimeException("Command failed ($cmd):\n$result"); + if (!empty($byInterface['HeadlessInterface']) && CIVICRM_UF !== 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['HeadlessInterface'])); + throw new \RuntimeException("Suite includes headless tests ($testNames) which require CIVICRM_UF=UnitTests.\n\n$blurb"); + } + if (!empty($byInterface['EndToEndInterface']) && CIVICRM_UF === 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['EndToEndInterface'])); + throw new \RuntimeException("Suite includes end-to-end tests ($testNames) which do not support CIVICRM_UF=UnitTests.\n\n$blurb"); + } } - switch ($decode) { - case 'raw': - return $result; - case 'phpcode': - // If the last output is /*PHPCODE*/, then we managed to complete execution. - if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { - throw new \RuntimeException("Command failed ($cmd):\n$result"); - } - return $result; + /** + * Call the "cv" command. + * + * This duplicates the standalone `cv()` wrapper that is recommended in bootstrap.php. + * This duplication is necessary because `cv()` is optional, and downstream implementers + * may alter, rename, or omit the wrapper, and (by virtue of its role in bootstrap) there + * it is impossible to define it centrally. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return string + * Response output (if the command executed normally). + * @throws \RuntimeException + * If the command terminates abnormally. + */ + protected function cv($cmd, $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => STDERR]; + $oldOutput = getenv('CV_OUTPUT'); + putenv("CV_OUTPUT=json"); + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; - case 'json': - return json_decode($result, 1); + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; - default: - throw new \RuntimeException("Bad decoder format ($decode)"); - } - } + case 'json': + return json_decode($result, 1); - /** - * @param $tests - * @return array - */ - protected function indexTestsByInterface($tests) { - $byInterface = array('HeadlessInterface' => array(), 'EndToEndInterface' => array()); - foreach ($tests as $test) { - /** @var \PHPUnit_Framework_Test $test */ - if ($test instanceof HeadlessInterface) { - $byInterface['HeadlessInterface'][get_class($test)] = 1; - } - if ($test instanceof EndToEndInterface) { - $byInterface['EndToEndInterface'][get_class($test)] = 1; + default: + throw new \RuntimeException("Bad decoder format ($decode)"); } } - return $byInterface; - } - /** - * Ensure that any tests have sensible groups, e.g. - * - * `HeadlessInterface` ==> `group headless` - * `EndToEndInterface` ==> `group e2e` - * - * @param array $byInterface - */ - protected function validateGroups($byInterface) { - foreach ($byInterface['HeadlessInterface'] as $className => $nonce) { - $clazz = new \ReflectionClass($className); - $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); - if (strpos($docComment, "@group headless\n") === FALSE) { - echo "WARNING: Class $className implements HeadlessInterface. It should declare \"@group headless\".\n"; - } - if (strpos($docComment, "@group e2e\n") !== FALSE) { - echo "WARNING: Class $className implements HeadlessInterface. It should not declare \"@group e2e\".\n"; + /** + * @param $tests + * @return array + */ + protected function indexTestsByInterface($tests) { + $byInterface = ['HeadlessInterface' => [], 'EndToEndInterface' => []]; + foreach ($tests as $test) { + /** @var \PHPUnit\Framework\Test $test */ + if ($test instanceof HeadlessInterface) { + $byInterface['HeadlessInterface'][get_class($test)] = 1; + } + if ($test instanceof EndToEndInterface) { + $byInterface['EndToEndInterface'][get_class($test)] = 1; + } } + return $byInterface; } - foreach ($byInterface['EndToEndInterface'] as $className => $nonce) { - $clazz = new \ReflectionClass($className); - $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); - if (strpos($docComment, "@group e2e\n") === FALSE) { - echo "WARNING: Class $className implements EndToEndInterface. It should declare \"@group e2e\".\n"; + + /** + * Ensure that any tests have sensible groups, e.g. + * + * `HeadlessInterface` ==> `group headless` + * `EndToEndInterface` ==> `group e2e` + * + * @param array $byInterface + */ + protected function validateGroups($byInterface) { + foreach ($byInterface['HeadlessInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group headless\n") === FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should declare \"@group headless\".\n"; + } + if (strpos($docComment, "@group e2e\n") !== FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should not declare \"@group e2e\".\n"; + } } - if (strpos($docComment, "@group headless\n") !== FALSE) { - echo "WARNING: Class $className implements EndToEndInterface. It should not declare \"@group headless\".\n"; + foreach ($byInterface['EndToEndInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group e2e\n") === FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should declare \"@group e2e\".\n"; + } + if (strpos($docComment, "@group headless\n") !== FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should not declare \"@group headless\".\n"; + } } } - } + } } diff --git a/Civi/Test/ContactTestTrait.php b/Civi/Test/ContactTestTrait.php new file mode 100644 index 000000000000..50e256177fb5 --- /dev/null +++ b/Civi/Test/ContactTestTrait.php @@ -0,0 +1,254 @@ +callAPISuccess()) and to the + * standard PHPUnit assertions ($this->assertEquals). It should + * not impose any other requirements for the downstream consumer class. + */ +trait ContactTestTrait { + + /** + * Emulate a logged in user since certain functions use that. + * value to store a record in the DB (like activity) + * CRM-8180 + * + * @return int + * Contact ID of the created user. + */ + public function createLoggedInUser() { + $params = [ + 'first_name' => 'Logged In', + 'last_name' => 'User ' . rand(), + 'contact_type' => 'Individual', + 'domain_id' => \CRM_Core_Config::domainID(), + ]; + $contactID = $this->individualCreate($params); + $this->callAPISuccess('UFMatch', 'create', [ + 'contact_id' => $contactID, + 'uf_name' => 'superman', + 'uf_id' => 6, + ]); + + $session = \CRM_Core_Session::singleton(); + $session->set('userID', $contactID); + return $contactID; + } + + /** + * Generic function to create Organisation, to be used in test cases + * + * @param array $params + * parameters for civicrm_contact_add api function call + * @param int $seq + * sequence number if creating multiple organizations + * + * @return int + * id of Organisation created + */ + public function organizationCreate($params = [], $seq = 0) { + if (!$params) { + $params = []; + } + $params = array_merge($this->sampleContact('Organization', $seq), $params); + return $this->_contactCreate($params); + } + + /** + * Generic function to create Individual, to be used in test cases + * + * @param array $params + * parameters for civicrm_contact_add api function call + * @param int $seq + * sequence number if creating multiple individuals + * @param bool $random + * + * @return int + * id of Individual created + * + * @throws \CRM_Core_Exception + */ + public function individualCreate($params = [], $seq = 0, $random = FALSE) { + $params = array_merge($this->sampleContact('Individual', $seq, $random), $params); + return $this->_contactCreate($params); + } + + /** + * Generic function to create Household, to be used in test cases + * + * @param array $params + * parameters for civicrm_contact_add api function call + * @param int $seq + * sequence number if creating multiple households + * + * @return int + * id of Household created + * + * @throws \CRM_Core_Exception + */ + public function householdCreate($params = [], $seq = 0) { + $params = array_merge($this->sampleContact('Household', $seq), $params); + return $this->_contactCreate($params); + } + + /** + * Helper function for getting sample contact properties. + * + * @param string $contact_type + * enum contact type: Individual, Organization + * @param int $seq + * sequence number for the values of this type + * @param bool $random + * + * @return array + * properties of sample contact (ie. $params for API call) + */ + public function sampleContact($contact_type, $seq = 0, $random = FALSE) { + $samples = [ + 'Individual' => [ + // The number of values in each list need to be coprime numbers to not have duplicates + 'first_name' => ['Anthony', 'Joe', 'Terrence', 'Lucie', 'Albert', 'Bill', 'Kim'], + 'middle_name' => ['J.', 'M.', 'P', 'L.', 'K.', 'A.', 'B.', 'C.', 'D', 'E.', 'Z.'], + 'last_name' => ['Anderson', 'Miller', 'Smith', 'Collins', 'Peterson'], + ], + 'Organization' => [ + 'organization_name' => [ + 'Unit Test Organization', + 'Acme', + 'Roberts and Sons', + 'Cryo Space Labs', + 'Sharper Pens', + ], + ], + 'Household' => [ + 'household_name' => ['Unit Test household'], + ], + ]; + $params = ['contact_type' => $contact_type]; + foreach ($samples[$contact_type] as $key => $values) { + $params[$key] = $values[$seq % count($values)]; + if ($random) { + $params[$key] .= substr(sha1(rand()), 0, 5); + } + } + if ($contact_type == 'Individual') { + $params['email'] = strtolower( + $params['first_name'] . '_' . $params['last_name'] . '@civicrm.org' + ); + $params['prefix_id'] = 3; + $params['suffix_id'] = 3; + } + return $params; + } + + /** + * Private helper function for calling civicrm_contact_add. + * + * @param array $params + * For civicrm_contact_add api function call. + * + * @return int + * id of Household created + * @throws \CRM_Core_Exception + * + */ + private function _contactCreate($params) { + $result = $this->callAPISuccess('contact', 'create', $params); + if (!empty($result['is_error']) || empty($result['id'])) { + throw new \CRM_Core_Exception('Could not create test contact, with message: ' . \CRM_Utils_Array::value('error_message', $result) . "\nBacktrace:" . \CRM_Utils_Array::value('trace', $result)); + } + return $result['id']; + } + + /** + * Delete contact, ensuring it is not the domain contact + * + * @param int $contactID + * Contact ID to delete + */ + public function contactDelete($contactID) { + $domain = new \CRM_Core_BAO_Domain(); + $domain->contact_id = $contactID; + if (!$domain->find(TRUE)) { + $this->callAPISuccess('contact', 'delete', [ + 'id' => $contactID, + 'skip_undelete' => 1, + ]); + } + } + + /** + * Add a Group. + * + * @param array $params + * + * @return int + * groupId of created group + */ + public function groupCreate($params = []) { + $params = array_merge([ + 'name' => 'Test Group 1', + 'domain_id' => 1, + 'title' => 'New Test Group Created', + 'description' => 'New Test Group Created', + 'is_active' => 1, + 'visibility' => 'Public Pages', + 'group_type' => [ + '1' => 1, + '2' => 1, + ], + ], $params); + + $result = $this->callAPISuccess('Group', 'create', $params); + return $result['id']; + } + + /** + * Delete a Group. + * + * @param int $gid + */ + public function groupDelete($gid) { + $params = [ + 'id' => $gid, + ]; + + $this->callAPISuccess('Group', 'delete', $params); + } + + /** + * Function to add a Group. + * + * @params array to add group + * + * @param int $groupID + * @param int $totalCount + * @param bool $random + * + * @return int + * groupId of created group + */ + public function groupContactCreate($groupID, $totalCount = 10, $random = FALSE) { + $params = ['group_id' => $groupID]; + for ($i = 1; $i <= $totalCount; $i++) { + $contactID = $this->individualCreate([], 0, $random); + if ($i == 1) { + $params += ['contact_id' => $contactID]; + } + else { + $params += ["contact_id.$i" => $contactID]; + } + } + $result = $this->callAPISuccess('GroupContact', 'create', $params); + + return $result; + } + +} diff --git a/Civi/Test/Data.php b/Civi/Test/Data.php index d1e62e5903fb..8b90b5d493a8 100644 --- a/Civi/Test/Data.php +++ b/Civi/Test/Data.php @@ -15,6 +15,9 @@ public function populate() { \Civi\Test::schema()->truncateAll(); \Civi\Test::schema()->setStrict(FALSE); + + // Ensure that when we populate the database it is done in utf8 mode + \Civi\Test::execute('SET NAMES utf8'); $sqlDir = dirname(dirname(__DIR__)) . "/sql"; $query2 = file_get_contents("$sqlDir/civicrm_data.mysql"); @@ -35,16 +38,16 @@ public function populate() { \Civi\Test::schema()->setStrict(TRUE); // Rebuild triggers - civicrm_api('system', 'flush', array('version' => 3, 'triggers' => 1)); + civicrm_api('system', 'flush', ['version' => 3, 'triggers' => 1]); - \CRM_Core_BAO_ConfigSetting::setEnabledComponents(array( + \CRM_Core_BAO_ConfigSetting::setEnabledComponents([ 'CiviEvent', 'CiviContribute', 'CiviMember', 'CiviMail', 'CiviReport', 'CiviPledge', - )); + ]); return TRUE; } diff --git a/Civi/Test/DbTestTrait.php b/Civi/Test/DbTestTrait.php new file mode 100644 index 000000000000..3e97931cfe07 --- /dev/null +++ b/Civi/Test/DbTestTrait.php @@ -0,0 +1,192 @@ + expected value. Empty if asserting + * that a DELETE occurred + * @delete boolean True if we're checking that a DELETE action occurred. + * @param $daoName + * @param $id + * @param $match + * @param bool $delete + * @throws \PHPUnit_Framework_AssertionFailedError + */ + public function assertDBState($daoName, $id, $match, $delete = FALSE) { + if (empty($id)) { + // adding this here since developers forget to check for an id + // and hence we get the first value in the db + $this->fail('ID not populated. Please fix your assertDBState usage!!!'); + } + + $object = new $daoName(); + $object->id = $id; + $verifiedCount = 0; + + // If we're asserting successful record deletion, make sure object is NOT found. + if ($delete) { + if ($object->find(TRUE)) { + $this->fail("Object not deleted by delete operation: $daoName, $id"); + } + return; + } + + // Otherwise check matches of DAO field values against expected values in $match. + if ($object->find(TRUE)) { + $fields = &$object->fields(); + foreach ($fields as $name => $value) { + $dbName = $value['name']; + if (isset($match[$name])) { + $verifiedCount++; + $this->assertEquals($object->$dbName, $match[$name]); + } + elseif (isset($match[$dbName])) { + $verifiedCount++; + $this->assertEquals($object->$dbName, $match[$dbName]); + } + } + } + else { + $this->fail("Could not retrieve object: $daoName, $id"); + } + + $matchSize = count($match); + if ($verifiedCount != $matchSize) { + $this->fail("Did not verify all fields in match array: $daoName, $id. Verified count = $verifiedCount. Match array size = $matchSize"); + } + } + + /** + * Request a record from the DB by seachColumn+searchValue. Success if a record is found. + * @param string $daoName + * @param $searchValue + * @param $returnColumn + * @param $searchColumn + * @param $message + * + * @return null|string + * @throws \PHPUnit_Framework_AssertionFailedError + */ + public function assertDBNotNull($daoName, $searchValue, $returnColumn, $searchColumn, $message) { + if (empty($searchValue)) { + $this->fail("empty value passed to assertDBNotNull"); + } + $value = \CRM_Core_DAO::getFieldValue($daoName, $searchValue, $returnColumn, $searchColumn, TRUE); + $this->assertNotNull($value, $message); + + return $value; + } + + /** + * Request a record from the DB by seachColumn+searchValue. Success if returnColumn value is NULL. + * @param string $daoName + * @param $searchValue + * @param $returnColumn + * @param $searchColumn + * @param $message + */ + public function assertDBNull($daoName, $searchValue, $returnColumn, $searchColumn, $message) { + $value = \CRM_Core_DAO::getFieldValue($daoName, $searchValue, $returnColumn, $searchColumn, TRUE); + $this->assertNull($value, $message); + } + + /** + * Request a record from the DB by id. Success if row not found. + * @param string $daoName + * @param int $id + * @param null $message + */ + public function assertDBRowNotExist($daoName, $id, $message = NULL) { + $message = $message ? $message : "$daoName (#$id) should not exist"; + $value = \CRM_Core_DAO::getFieldValue($daoName, $id, 'id', 'id', TRUE); + $this->assertNull($value, $message); + } + + /** + * Request a record from the DB by id. Success if row not found. + * @param string $daoName + * @param int $id + * @param null $message + */ + public function assertDBRowExist($daoName, $id, $message = NULL) { + $message = $message ? $message : "$daoName (#$id) should exist"; + $value = \CRM_Core_DAO::getFieldValue($daoName, $id, 'id', 'id', TRUE); + $this->assertEquals($id, $value, $message); + } + + /** + * Compare a single column value in a retrieved DB record to an expected value. + * @param string $daoName + * @param $searchValue + * @param $returnColumn + * @param $searchColumn + * @param $expectedValue + * @param $message + */ + public function assertDBCompareValue( + $daoName, $searchValue, $returnColumn, $searchColumn, + $expectedValue, $message + ) { + $value = \CRM_Core_DAO::getFieldValue($daoName, $searchValue, $returnColumn, $searchColumn, TRUE); + $this->assertEquals($expectedValue, $value, $message); + } + + /** + * Compare all values in a single retrieved DB record to an array of expected values. + * @param string $daoName + * @param array $searchParams + * @param $expectedValues + */ + public function assertDBCompareValues($daoName, $searchParams, $expectedValues) { + //get the values from db + $dbValues = array(); + \CRM_Core_DAO::commonRetrieve($daoName, $searchParams, $dbValues); + + // compare db values with expected values + $this->assertAttributesEquals($expectedValues, $dbValues); + } + + /** + * Assert that a SQL query returns a given value. + * + * The first argument is an expected value. The remaining arguments are passed + * to CRM_Core_DAO::singleValueQuery + * + * Example: $this->assertSql(2, 'select count(*) from foo where foo.bar like "%1"', + * array(1 => array("Whiz", "String"))); + * + * @param $expected + * @param $query + * @param array $params + * @param string $message + * + * @throws \Exception + */ + public function assertDBQuery($expected, $query, $params = array(), $message = '') { + if ($message) { + $message .= ': '; + } + $actual = \CRM_Core_DAO::singleValueQuery($query, $params); + $this->assertEquals($expected, $actual, + sprintf('%sexpected=[%s] actual=[%s] query=[%s]', + $message, $expected, $actual, \CRM_Core_DAO::composeQuery($query, $params, FALSE) + ) + ); + } + +} diff --git a/Civi/Test/GenericAssertionsTrait.php b/Civi/Test/GenericAssertionsTrait.php new file mode 100644 index 000000000000..993e1278cbbb --- /dev/null +++ b/Civi/Test/GenericAssertionsTrait.php @@ -0,0 +1,125 @@ +assertInternalType($expected, $actual, $message); + } + + /** + * Assert that two array-trees are exactly equal. + * + * The ordering of keys do not affect the outcome (within either the roots + * or in any child elements). + * + * Error messages will reveal a readable -path-, regardless of how many + * levels of nesting are present. + * + * @param array $expected + * @param array $actual + */ + public function assertTreeEquals($expected, $actual) { + $e = array(); + $a = array(); + \CRM_Utils_Array::flatten($expected, $e, '', ':::'); + \CRM_Utils_Array::flatten($actual, $a, '', ':::'); + ksort($e); + ksort($a); + + $this->assertEquals($e, $a); + } + + /** + * Assert that two numbers are approximately equal, + * give or take some $tolerance. + * + * @param int|float $expected + * @param int|float $actual + * @param int|float $tolerance + * Any differences <$tolerance are considered irrelevant. + * Differences >=$tolerance are considered relevant. + * @param string $message + */ + public function assertApproxEquals($expected, $actual, $tolerance, $message = NULL) { + $diff = abs($actual - $expected); + if ($message === NULL) { + $message = sprintf("approx-equals: expected=[%.3f] actual=[%.3f] diff=[%.3f] tolerance=[%.3f]", $expected, $actual, $diff, $tolerance); + } + $this->assertTrue($diff < $tolerance, $message); + } + + /** + * Assert attributes are equal. + * + * @param array $expectedValues + * @param array $actualValues + * @param string $message + * + * @throws \PHPUnit_Framework_AssertionFailedError + */ + public function assertAttributesEquals($expectedValues, $actualValues, $message = NULL) { + foreach ($expectedValues as $paramName => $paramValue) { + if (isset($actualValues[$paramName])) { + $this->assertEquals($paramValue, $actualValues[$paramName], "Value Mismatch On $paramName - value 1 is " . print_r($paramValue, TRUE) . " value 2 is " . print_r($actualValues[$paramName], TRUE)); + } + else { + $this->assertNull($expectedValues[$paramName], "Attribute '$paramName' not present in actual array and we expected it to be " . $expectedValues[$paramName]); + } + } + } + + /** + * @param string|int $key + * @param array $list + */ + public function assertArrayKeyExists($key, &$list) { + $result = isset($list[$key]) ? TRUE : FALSE; + $this->assertTrue($result, sprintf("%s element exists?", $key)); + } + + /** + * @param string|int $key + * @param array $list + */ + public function assertArrayValueNotNull($key, &$list) { + $this->assertArrayKeyExists($key, $list); + + $value = isset($list[$key]) ? $list[$key] : NULL; + $this->assertTrue($value, + sprintf("%s element not null?", $key) + ); + } + + /** + * Assert the 2 arrays have the same values. + * + * The order of arrays, and keys of the arrays, do not affect the outcome. + * + * @param array $array1 + * @param array $array2 + */ + public function assertArrayValuesEqual($array1, $array2) { + $array1 = array_values($array1); + $array2 = array_values($array2); + sort($array1); + sort($array2); + $this->assertEquals($array1, $array2); + } + +} diff --git a/Civi/Test/Legacy/CiviTestListener.php b/Civi/Test/Legacy/CiviTestListener.php new file mode 100644 index 000000000000..015f1af208e6 --- /dev/null +++ b/Civi/Test/Legacy/CiviTestListener.php @@ -0,0 +1,294 @@ + Array(string $hookName => string $methodName)). + */ + private $cache = []; + + /** + * @var \CRM_Core_Transaction|NULL + */ + private $tx; + + public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { + $byInterface = $this->indexTestsByInterface($suite->tests()); + $this->validateGroups($byInterface); + $this->autoboot($byInterface); + } + + public function endTestSuite(\PHPUnit_Framework_TestSuite $suite) { + $this->cache = []; + } + + public function startTest(\PHPUnit_Framework_Test $test) { + if ($this->isCiviTest($test)) { + error_reporting(E_ALL); + $this->errorScope = \CRM_Core_TemporaryErrorScope::useException(); + } + + if ($test instanceof \Civi\Test\HeadlessInterface) { + $this->bootHeadless($test); + } + + if ($test instanceof \Civi\Test\HookInterface) { + // Note: bootHeadless() indirectly resets any hooks, which means that hook_civicrm_config + // is unsubscribable. However, after bootHeadless(), we're free to subscribe to hooks again. + $this->registerHooks($test); + } + if ($test instanceof \Civi\Test\TransactionalInterface) { + $this->tx = new \CRM_Core_Transaction(TRUE); + $this->tx->rollback(); + } + else { + $this->tx = NULL; + } + } + + public function endTest(\PHPUnit_Framework_Test $test, $time) { + if ($test instanceof \Civi\Test\TransactionalInterface) { + $this->tx->rollback()->commit(); + $this->tx = NULL; + } + if ($test instanceof \Civi\Test\HookInterface) { + \CRM_Utils_Hook::singleton()->reset(); + } + if ($this->isCiviTest($test)) { + error_reporting(E_ALL & ~E_NOTICE); + $this->errorScope = NULL; + } + } + + /** + * @param HeadlessInterface|\PHPUnit_Framework_Test $test + */ + protected function bootHeadless($test) { + if (CIVICRM_UF !== 'UnitTests') { + throw new \RuntimeException('HeadlessInterface requires CIVICRM_UF=UnitTests'); + } + + // Hrm, this seems wrong. Shouldn't we be resetting the entire session? + $session = \CRM_Core_Session::singleton(); + $session->set('userID', NULL); + + $test->setUpHeadless(); + + \CRM_Utils_System::flushCache(); + \Civi::reset(); + \CRM_Core_Session::singleton()->set('userID', NULL); + // ugh, performance + $config = \CRM_Core_Config::singleton(TRUE, TRUE); + + if (property_exists($config->userPermissionClass, 'permissions')) { + $config->userPermissionClass->permissions = NULL; + } + } + + /** + * @param \Civi\Test\HookInterface $test + * @return array + * Array(string $hookName => string $methodName)). + */ + protected function findTestHooks(\Civi\Test\HookInterface $test) { + $class = get_class($test); + if (!isset($this->cache[$class])) { + $funcs = []; + foreach (get_class_methods($class) as $func) { + if (preg_match('/^hook_/', $func)) { + $funcs[substr($func, 5)] = $func; + } + } + $this->cache[$class] = $funcs; + } + return $this->cache[$class]; + } + + /** + * @param \PHPUnit_Framework_Test $test + * @return bool + */ + protected function isCiviTest(\PHPUnit_Framework_Test $test) { + return $test instanceof \Civi\Test\HookInterface || $test instanceof \Civi\Test\HeadlessInterface; + } + + /** + * Find any hook functions in $test and register them. + * + * @param \Civi\Test\HookInterface $test + */ + protected function registerHooks(\Civi\Test\HookInterface $test) { + if (CIVICRM_UF !== 'UnitTests') { + // This is not ideal -- it's just a side-effect of how hooks and E2E tests work. + // We can temporarily subscribe to hooks in-process, but for other processes, it gets messy. + throw new \RuntimeException('CiviHookTestInterface requires CIVICRM_UF=UnitTests'); + } + \CRM_Utils_Hook::singleton()->reset(); + /** @var \CRM_Utils_Hook_UnitTests $hooks */ + $hooks = \CRM_Utils_Hook::singleton(); + foreach ($this->findTestHooks($test) as $hook => $func) { + $hooks->setHook($hook, [$test, $func]); + } + } + + /** + * The first time we come across HeadlessInterface or EndToEndInterface, we'll + * try to autoboot. + * + * Once the system is booted, there's nothing we can do -- we're stuck with that + * environment. (Thank you, prolific define()s!) If there's a conflict between a + * test-class and the active boot-level, then we'll have to bail. + * + * @param array $byInterface + * List of test classes, keyed by major interface (HeadlessInterface vs EndToEndInterface). + */ + protected function autoboot($byInterface) { + if (defined('CIVICRM_UF')) { + // OK, nothing we can do. System has booted already. + } + elseif (!empty($byInterface['HeadlessInterface'])) { + putenv('CIVICRM_UF=UnitTests'); + // phpcs:disable + eval($this->cv('php:boot --level=full', 'phpcode')); + // phpcs:enable + } + elseif (!empty($byInterface['EndToEndInterface'])) { + putenv('CIVICRM_UF='); + // phpcs:disable + eval($this->cv('php:boot --level=full', 'phpcode')); + // phpcs:enable + } + + $blurb = "Tip: Run the headless tests and end-to-end tests separately, e.g.\n" + . " $ phpunit5 --group headless\n" + . " $ phpunit5 --group e2e \n"; + + if (!empty($byInterface['HeadlessInterface']) && CIVICRM_UF !== 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['HeadlessInterface'])); + throw new \RuntimeException("Suite includes headless tests ($testNames) which require CIVICRM_UF=UnitTests.\n\n$blurb"); + } + if (!empty($byInterface['EndToEndInterface']) && CIVICRM_UF === 'UnitTests') { + $testNames = implode(', ', array_keys($byInterface['EndToEndInterface'])); + throw new \RuntimeException("Suite includes end-to-end tests ($testNames) which do not support CIVICRM_UF=UnitTests.\n\n$blurb"); + } + } + + /** + * Call the "cv" command. + * + * This duplicates the standalone `cv()` wrapper that is recommended in bootstrap.php. + * This duplication is necessary because `cv()` is optional, and downstream implementers + * may alter, rename, or omit the wrapper, and (by virtue of its role in bootstrap) there + * it is impossible to define it centrally. + * + * @param string $cmd + * The rest of the command to send. + * @param string $decode + * Ex: 'json' or 'phpcode'. + * @return string + * Response output (if the command executed normally). + * @throws \RuntimeException + * If the command terminates abnormally. + */ + protected function cv($cmd, $decode = 'json') { + $cmd = 'cv ' . $cmd; + $descriptorSpec = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => STDERR]; + $oldOutput = getenv('CV_OUTPUT'); + putenv("CV_OUTPUT=json"); + $process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__); + putenv("CV_OUTPUT=$oldOutput"); + fclose($pipes[0]); + $result = stream_get_contents($pipes[1]); + fclose($pipes[1]); + if (proc_close($process) !== 0) { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + switch ($decode) { + case 'raw': + return $result; + + case 'phpcode': + // If the last output is /*PHPCODE*/, then we managed to complete execution. + if (substr(trim($result), 0, 12) !== "/*BEGINPHP*/" || substr(trim($result), -10) !== "/*ENDPHP*/") { + throw new \RuntimeException("Command failed ($cmd):\n$result"); + } + return $result; + + case 'json': + return json_decode($result, 1); + + default: + throw new \RuntimeException("Bad decoder format ($decode)"); + } + } + + /** + * @param $tests + * @return array + */ + protected function indexTestsByInterface($tests) { + $byInterface = ['HeadlessInterface' => [], 'EndToEndInterface' => []]; + foreach ($tests as $test) { + /** @var \PHPUnit_Framework_Test $test */ + if ($test instanceof \Civi\Test\HeadlessInterface) { + $byInterface['HeadlessInterface'][get_class($test)] = 1; + } + if ($test instanceof \Civi\Test\EndToEndInterface) { + $byInterface['EndToEndInterface'][get_class($test)] = 1; + } + } + return $byInterface; + } + + /** + * Ensure that any tests have sensible groups, e.g. + * + * `HeadlessInterface` ==> `group headless` + * `EndToEndInterface` ==> `group e2e` + * + * @param array $byInterface + */ + protected function validateGroups($byInterface) { + foreach ($byInterface['HeadlessInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group headless\n") === FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should declare \"@group headless\".\n"; + } + if (strpos($docComment, "@group e2e\n") !== FALSE) { + echo "WARNING: Class $className implements HeadlessInterface. It should not declare \"@group e2e\".\n"; + } + } + foreach ($byInterface['EndToEndInterface'] as $className => $nonce) { + $clazz = new \ReflectionClass($className); + $docComment = str_replace("\r\n", "\n", $clazz->getDocComment()); + if (strpos($docComment, "@group e2e\n") === FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should declare \"@group e2e\".\n"; + } + if (strpos($docComment, "@group headless\n") !== FALSE) { + echo "WARNING: Class $className implements EndToEndInterface. It should not declare \"@group headless\".\n"; + } + } + } + +} diff --git a/Civi/Test/MailingTestTrait.php b/Civi/Test/MailingTestTrait.php new file mode 100644 index 000000000000..6609750fbebc --- /dev/null +++ b/Civi/Test/MailingTestTrait.php @@ -0,0 +1,45 @@ + 'maild' . rand(), + 'body_text' => 'bdkfhdskfhduew{domain.address}{action.optOutUrl}', + 'name' => 'mailing name' . rand(), + 'created_id' => 1, + ), $params); + + $result = $this->callAPISuccess('Mailing', 'create', $params); + return $result['id']; + } + + /** + * Helper function to delete mailing. + * @param $id + */ + public function deleteMailing($id) { + $params = array( + 'id' => $id, + ); + + $this->callAPISuccess('Mailing', 'delete', $params); + } + +} diff --git a/Civi/Test/Schema.php b/Civi/Test/Schema.php index 7c3bb4655dbe..b5a9aa525cab 100644 --- a/Civi/Test/Schema.php +++ b/Civi/Test/Schema.php @@ -25,7 +25,7 @@ public function getTables($type) { $pdo->quote($type) ); $tables = $pdo->query($query); - $result = array(); + $result = []; foreach ($tables as $table) { $result[] = $table['table_name']; } @@ -35,20 +35,20 @@ public function getTables($type) { public function setStrict($checks) { $dbName = \Civi\Test::dsn('database'); if ($checks) { - $queries = array( + $queries = [ "USE {$dbName};", "SET global innodb_flush_log_at_trx_commit = 1;", "SET SQL_MODE='STRICT_ALL_TABLES';", "SET foreign_key_checks = 1;", - ); + ]; } else { - $queries = array( + $queries = [ "USE {$dbName};", "SET foreign_key_checks = 0", "SET SQL_MODE='STRICT_ALL_TABLES';", "SET global innodb_flush_log_at_trx_commit = 2;", - ); + ]; } foreach ($queries as $query) { if (\Civi\Test::execute($query) === FALSE) { @@ -59,7 +59,7 @@ public function setStrict($checks) { } public function dropAll() { - $queries = array(); + $queries = []; foreach ($this->getTables('VIEW') as $table) { if (preg_match('/^(civicrm_|log_)/', $table)) { $queries[] = "DROP VIEW $table"; @@ -89,8 +89,8 @@ public function dropAll() { public function truncateAll() { $tables = \Civi\Test::schema()->getTables('BASE TABLE'); - $truncates = array(); - $drops = array(); + $truncates = []; + $drops = []; foreach ($tables as $table) { // skip log tables if (substr($table, 0, 4) == 'log_') { diff --git a/Civi/Test/TAP.php b/Civi/Test/TAP.php new file mode 100644 index 000000000000..5b432ac9530d --- /dev/null +++ b/Civi/Test/TAP.php @@ -0,0 +1,256 @@ +write("TAP version 13\n"); + } + + /** + * An error occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + */ + public function addError(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->writeNotOk($test, 'Error'); + } + + /** + * A failure occurred. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\AssertionFailedError $e + * @param float $time + */ + public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, $time) { + $this + ->writeNotOk($test, 'Failure'); + $message = explode("\n", \PHPUnit\Framework\TestFailure::exceptionToString($e)); + $diagnostic = array( + 'message' => $message[0], + 'severity' => 'fail', + ); + if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) { + $cf = $e + ->getComparisonFailure(); + if ($cf !== NULL) { + $diagnostic['data'] = array( + 'got' => $cf + ->getActual(), + 'expected' => $cf + ->getExpected(), + ); + } + } + + if (function_exists('yaml_emit')) { + $content = \yaml_emit($diagnostic, YAML_UTF8_ENCODING); + $content = ' ' . strtr($content, ["\n" => "\n "]); + } + else { + // Any valid JSON document is a valid YAML document. + $content = json_encode($diagnostic, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + // For closest match, drop outermost {}'s. Realign indentation. + $content = substr($content, 0, strrpos($content, "}")) . ' }'; + $content = ' ' . ltrim($content); + $content = sprintf(" ---\n%s\n ...\n", $content); + } + + $this->write($content); + } + + /** + * Incomplete test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + */ + public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->writeNotOk($test, '', 'TODO Incomplete Test'); + } + + /** + * Risky test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + * + * @since Method available since Release 4.0.0 + */ + public function addRiskyTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->write(sprintf("ok %d - # RISKY%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * Skipped test. + * + * @param \PHPUnit\Framework\Test $test + * @param \Exception $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest(\PHPUnit\Framework\Test $test, \Exception $e, $time) { + $this + ->write(sprintf("ok %d - # SKIP%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * Warning test. + * + * @param \PHPUnit\Framework\Test $test + * @param \PHPUnit\Framework\Warning $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, $time) { + $this + ->write(sprintf("ok %d - # Warning%s\n", $this->testNumber, $e + ->getMessage() != '' ? ' ' . $e + ->getMessage() : '')); + $this->testSuccessful = FALSE; + } + + /** + * A testsuite started. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $this->testSuiteLevel++; + } + + /** + * A testsuite ended. + * + * @param \PHPUnit\Framework\TestSuite $suite + */ + public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) { + $this->testSuiteLevel--; + if ($this->testSuiteLevel == 0) { + $this + ->write(sprintf("1..%d\n", $this->testNumber)); + } + } + + /** + * A test started. + * + * @param \PHPUnit\Framework\Test $test + */ + public function startTest(\PHPUnit\Framework\Test $test) { + $this->testNumber++; + $this->testSuccessful = TRUE; + } + + /** + * A test ended. + * + * @param \PHPUnit\Framework\Test $test + * @param float $time + */ + public function endTest(\PHPUnit\Framework\Test $test, $time) { + if ($this->testSuccessful === TRUE) { + $this + ->write(sprintf("ok %d - %s\n", $this->testNumber, \PHPUnit\Util\Test::describe($test))); + } + $this + ->writeDiagnostics($test); + } + + /** + * @param \PHPUnit\Framework\Test $test + * @param string $prefix + * @param string $directive + */ + protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '') { + $this + ->write(sprintf("not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describe($test), $directive != '' ? ' # ' . $directive : '')); + $this->testSuccessful = FALSE; + } + + /** + * @param \PHPUnit\Framework\Test $test + */ + private function writeDiagnostics(\PHPUnit\Framework\Test $test) { + if (!$test instanceof \PHPUnit\Framework\TestCase) { + return; + } + if (!$test + ->hasOutput()) { + return; + } + foreach (explode("\n", trim($test + ->getActualOutput())) as $line) { + $this + ->write(sprintf("# %s\n", $line)); + } + } + +} diff --git a/Civi/Token/AbstractTokenSubscriber.php b/Civi/Token/AbstractTokenSubscriber.php index 1164032de84c..012d306930ae 100644 --- a/Civi/Token/AbstractTokenSubscriber.php +++ b/Civi/Token/AbstractTokenSubscriber.php @@ -1,9 +1,9 @@ 'registerTokens', Events::TOKEN_EVALUATE => 'evaluateTokens', \Civi\ActionSchedule\Events::MAILING_QUERY => 'alterActionScheduleQuery', - ); + ]; } /** @@ -70,16 +71,24 @@ public static function getSubscribedEvents() { /** * @var array - * Ex: array('viewUrl', 'editUrl'). + * List of tokens provided by this class + * Array(string $fieldName => string $label). */ public $tokenNames; + /** + * @var array + * List of active tokens - tokens provided by this class and used in the message + * Array(string $tokenName); + */ + public $activeTokens; + /** * @param $entity * @param array $tokenNames - * Array(string $fieldName => string $label). + * Array(string $tokenName => string $label). */ - public function __construct($entity, $tokenNames = array()) { + public function __construct($entity, $tokenNames = []) { $this->entity = $entity; $this->tokenNames = $tokenNames; } @@ -101,7 +110,7 @@ public function checkActive(\Civi\Token\TokenProcessor $processor) { /** * Register the declared tokens. * - * @param TokenRegisterEvent $e + * @param \Civi\Token\Event\TokenRegisterEvent $e * The registration event. Add new tokens using register(). */ public function registerTokens(TokenRegisterEvent $e) { @@ -109,30 +118,14 @@ public function registerTokens(TokenRegisterEvent $e) { return; } foreach ($this->tokenNames as $name => $label) { - $e->register(array( + $e->register([ 'entity' => $this->entity, 'field' => $name, 'label' => $label, - )); + ]); } } - /** - * Get all custom field tokens of $entity - * - * @param string $entity - * @return array $customTokens - * return custom field tokens in array('custom_N' => 'label') format - */ - public function getCustomTokens($entity) { - $customTokens = array(); - foreach (\CRM_Core_BAO_CustomField::getFields($entity) as $id => $info) { - $customTokens["custom_$id"] = $info['label']; - } - - return $customTokens; - } - /** * Alter the query which prepopulates mailing data * for scheduled reminders. @@ -140,7 +133,7 @@ public function getCustomTokens($entity) { * This is method is not always appropriate, but if you're specifically * focused on scheduled reminders, it can be convenient. * - * @param MailingQueryEvent $e + * @param \Civi\ActionSchedule\Event\MailingQueryEvent $e * The pending query which may be modified. See discussion on * MailingQueryEvent::$query. */ @@ -150,7 +143,7 @@ public function alterActionScheduleQuery(MailingQueryEvent $e) { /** * Populate the token data. * - * @param TokenValueEvent $e + * @param \Civi\Token\Event\TokenValueEvent $e * The event, which includes a list of rows and tokens. */ public function evaluateTokens(TokenValueEvent $e) { @@ -158,22 +151,34 @@ public function evaluateTokens(TokenValueEvent $e) { return; } - $messageTokens = $e->getTokenProcessor()->getMessageTokens(); - if (!isset($messageTokens[$this->entity])) { + $this->activeTokens = $this->getActiveTokens($e); + if (!$this->activeTokens) { return; } - - $activeTokens = array_intersect($messageTokens[$this->entity], array_keys($this->tokenNames)); - $prefetch = $this->prefetch($e); foreach ($e->getRows() as $row) { - foreach ((array) $activeTokens as $field) { + foreach ($this->activeTokens as $field) { $this->evaluateToken($row, $this->entity, $field, $prefetch); } } } + /** + * To handle variable tokens, override this function and return the active tokens. + * + * @param \Civi\Token\Event\TokenValueEvent $e + * + * @return mixed + */ + public function getActiveTokens(TokenValueEvent $e) { + $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + if (!isset($messageTokens[$this->entity])) { + return FALSE; + } + return array_intersect($messageTokens[$this->entity], array_keys($this->tokenNames)); + } + /** * To perform a bulk lookup before rendering tokens, override this * function and return the prefetched data. @@ -199,6 +204,6 @@ public function prefetch(TokenValueEvent $e) { * Any data that was returned by the prefetch(). * @return mixed */ - public abstract function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL); + abstract public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL); } diff --git a/Civi/Token/Event/TokenRegisterEvent.php b/Civi/Token/Event/TokenRegisterEvent.php index ff9122543c43..c7fdfa01fe7b 100644 --- a/Civi/Token/Event/TokenRegisterEvent.php +++ b/Civi/Token/Event/TokenRegisterEvent.php @@ -57,10 +57,10 @@ public function register($paramsOrField, $label = NULL) { $params = $paramsOrField; } else { - $params = array( + $params = [ 'field' => $paramsOrField, 'label' => $label, - ); + ]; } $params = array_merge($this->defaults, $params); $this->tokenProcessor->addToken($params); diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index d00acd750922..672080d29ba4 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -24,16 +24,16 @@ class TokenCompatSubscriber implements EventSubscriberInterface { * @inheritDoc */ public static function getSubscribedEvents() { - return array( + return [ Events::TOKEN_EVALUATE => 'onEvaluate', Events::TOKEN_RENDER => 'onRender', - ); + ]; } /** * Load token data. * - * @param TokenValueEvent $e + * @param \Civi\Token\Event\TokenValueEvent $e * @throws TokenException */ public function onEvaluate(TokenValueEvent $e) { @@ -41,7 +41,7 @@ public function onEvaluate(TokenValueEvent $e) { // hook *categories* (aka entities aka namespaces). We'll cache // this in the TokenProcessor's context. - $hookTokens = array(); + $hookTokens = []; \CRM_Utils_Hook::tokens($hookTokens); $categories = array_keys($hookTokens); $e->getTokenProcessor()->context['hookTokenCategories'] = $categories; @@ -49,14 +49,18 @@ public function onEvaluate(TokenValueEvent $e) { $messageTokens = $e->getTokenProcessor()->getMessageTokens(); foreach ($e->getRows() as $row) { + if (empty($row->context['contactId'])) { + continue; + } /** @var int $contactId */ $contactId = $row->context['contactId']; if (empty($row->context['contact'])) { - $params = array( - array('contact_id', '=', $contactId, 0, 0), - ); + $params = [ + ['contact_id', '=', $contactId, 0, 0], + ]; list($contact, $_) = \CRM_Contact_BAO_Query::apiQuery($params); - $contact = reset($contact); //CRM-4524 + //CRM-4524 + $contact = reset($contact); if (!$contact || is_a($contact, 'CRM_Core_Error')) { // FIXME: Need to differentiate errors which kill the batch vs the individual row. throw new TokenException("Failed to generate token data. Invalid contact ID: " . $row->context['contactId']); @@ -66,10 +70,10 @@ public function onEvaluate(TokenValueEvent $e) { if (!empty($messageTokens['contact'])) { foreach ($messageTokens['contact'] as $token) { if (\CRM_Core_BAO_CustomField::getKeyID($token)) { - $contact[$token] = civicrm_api3('Contact', 'getvalue', array( + $contact[$token] = civicrm_api3('Contact', 'getvalue', [ 'return' => $token, 'id' => $contactId, - )); + ]); } } } @@ -84,13 +88,13 @@ public function onEvaluate(TokenValueEvent $e) { $contact = array_merge($contact, $row->context['tmpTokenParams']); } - $contactArray = !is_array($contactId) ? array($contactId => $contact) : $contact; + $contactArray = !is_array($contactId) ? [$contactId => $contact] : $contact; // Note: This is a small contract change from the past; data should be missing // less randomly. \CRM_Utils_Hook::tokenValues($contactArray, (array) $contactId, - empty($row->context['mailingJob']) ? NULL : $row->context['mailingJob']->id, + empty($row->context['mailingJobId']) ? NULL : $row->context['mailingJobId'], $messageTokens, $row->context['controller'] ); @@ -106,7 +110,7 @@ public function onEvaluate(TokenValueEvent $e) { /** * Apply the various CRM_Utils_Token helpers. * - * @param TokenRenderEvent $e + * @param \Civi\Token\Event\TokenRenderEvent $e */ public function onRender(TokenRenderEvent $e) { $isHtml = ($e->message['format'] == 'text/html'); diff --git a/Civi/Token/TokenProcessor.php b/Civi/Token/TokenProcessor.php index 99a0f0f9c43d..1364ed714faa 100644 --- a/Civi/Token/TokenProcessor.php +++ b/Civi/Token/TokenProcessor.php @@ -4,7 +4,6 @@ use Civi\Token\Event\TokenRegisterEvent; use Civi\Token\Event\TokenRenderEvent; use Civi\Token\Event\TokenValueEvent; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Traversable; class TokenProcessor { @@ -25,11 +24,15 @@ class TokenProcessor { * automatically from contactId.) * - actionSchedule: DAO, the rule which triggered the mailing * [for CRM_Core_BAO_ActionScheduler]. + * - schema: array, a list of fields that will be provided for each row. + * This is automatically populated with any general context + * keys, but you may need to add extra keys for token-row data. + * ex: ['contactId', 'activityId']. */ public $context; /** - * @var EventDispatcherInterface + * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ protected $dispatcher; @@ -68,13 +71,23 @@ class TokenProcessor { */ protected $tokens = NULL; + /** + * A list of available tokens formatted for display + * @var array + * Array('{' . $dottedName . '}' => 'labelString') + */ + protected $listTokens = NULL; + protected $next = 0; /** - * @param EventDispatcherInterface $dispatcher + * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher * @param array $context */ public function __construct($dispatcher, $context) { + $context['schema'] = isset($context['schema']) + ? array_unique(array_merge($context['schema'], array_keys($context))) + : array_keys($context); $this->dispatcher = $dispatcher; $this->context = $context; } @@ -91,11 +104,11 @@ public function __construct($dispatcher, $context) { * @return TokenProcessor */ public function addMessage($name, $value, $format) { - $this->messages[$name] = array( + $this->messages[$name] = [ 'string' => $value, 'format' => $format, 'tokens' => \CRM_Utils_Token::getTokens($value), - ); + ]; return $this; } @@ -106,11 +119,11 @@ public function addMessage($name, $value, $format) { */ public function addRow() { $key = $this->next++; - $this->rowContexts[$key] = array(); - $this->rowValues[$key] = array( - 'text/plain' => array(), - 'text/html' => array(), - ); + $this->rowContexts[$key] = []; + $this->rowValues[$key] = [ + 'text/plain' => [], + 'text/html' => [], + ]; return new TokenRow($this, $key); } @@ -146,7 +159,7 @@ public function getMessage($name) { * @return array */ public function getMessageTokens() { - $tokens = array(); + $tokens = []; foreach ($this->messages as $message) { $tokens = \CRM_Utils_Array::crmArrayMerge($tokens, $message['tokens']); } @@ -168,6 +181,44 @@ public function getRows() { return new TokenRowIterator($this, new \ArrayIterator($this->rowContexts)); } + /** + * Get a list of all unique values for a given context field, + * whether defined at the processor or row level. + * + * @param string $field + * Ex: 'contactId'. + * @param $subfield + * @return array + * Ex: [12, 34, 56]. + */ + public function getContextValues($field, $subfield = NULL) { + $values = []; + if (isset($this->context[$field])) { + if ($subfield) { + if (isset($this->context[$field]->$subfield)) { + $values[] = $this->context[$field]->$subfield; + } + } + else { + $values[] = $this->context[$field]; + } + } + foreach ($this->getRows() as $row) { + if (isset($row->context[$field])) { + if ($subfield) { + if (isset($row->context[$field]->$subfield)) { + $values[] = $row->context[$field]->$subfield; + } + } + else { + $values[] = $row->context[$field]; + } + } + } + $values = array_unique($values); + return $values; + } + /** * Get the list of available tokens. * @@ -176,13 +227,29 @@ public function getRows() { */ public function getTokens() { if ($this->tokens === NULL) { - $this->tokens = array(); - $event = new TokenRegisterEvent($this, array('entity' => 'undefined')); + $this->tokens = []; + $event = new TokenRegisterEvent($this, ['entity' => 'undefined']); $this->dispatcher->dispatch(Events::TOKEN_REGISTER, $event); } return $this->tokens; } + /** + * Get the list of available tokens, formatted for display + * + * @return array + * Ex: $tokens[ '{token.name}' ] = "Token label" + */ + public function listTokens() { + if ($this->listTokens === NULL) { + $this->listTokens = []; + foreach ($this->getTokens() as $token => $values) { + $this->listTokens['{' . $token . '}'] = $values['label']; + } + } + return $this->listTokens; + } + /** * Compute and store token values. */ @@ -211,11 +278,13 @@ public function render($name, $row) { $row->fill($message['format']); $useSmarty = !empty($row->context['smarty']); - // FIXME preg_callback. + /** + *@FIXME preg_callback. + */ $tokens = $this->rowValues[$row->tokenRow][$message['format']]; - $flatTokens = array(); + $flatTokens = []; \CRM_Utils_Array::flatten($tokens, $flatTokens, '', '.'); - $filteredTokens = array(); + $filteredTokens = []; foreach ($flatTokens as $k => $v) { $filteredTokens['{' . $k . '}'] = ($useSmarty ? \CRM_Utils_Token::tokenEscapeSmarty($v) : $v); } @@ -237,10 +306,11 @@ class TokenRowIterator extends \IteratorIterator { /** * @param TokenProcessor $tokenProcessor - * @param Traversable $iterator + * @param \Traversable $iterator */ public function __construct(TokenProcessor $tokenProcessor, Traversable $iterator) { - parent::__construct($iterator); // TODO: Change the autogenerated stub + // TODO: Change the autogenerated stub + parent::__construct($iterator); $this->tokenProcessor = $tokenProcessor; } diff --git a/Civi/Token/TokenRow.php b/Civi/Token/TokenRow.php index 4f186d3d5c1e..5287219a9401 100644 --- a/Civi/Token/TokenRow.php +++ b/Civi/Token/TokenRow.php @@ -65,7 +65,8 @@ class TokenRow { public function __construct(TokenProcessor $tokenProcessor, $key) { $this->tokenProcessor = $tokenProcessor; $this->tokenRow = $key; - $this->format('text/plain'); // Set a default. + // Set a default. + $this->format('text/plain'); $this->context = new TokenRowContext($tokenProcessor, $key); } @@ -136,13 +137,14 @@ public function tokens($a = NULL, $b = NULL, $c = NULL) { */ public function customToken($entity, $customFieldID, $entityID) { $customFieldName = "custom_" . $customFieldID; - $fieldValue = civicrm_api3($entity, 'getvalue', array( + $record = civicrm_api3($entity, "getSingle", [ 'return' => $customFieldName, 'id' => $entityID, - )); + ]); + $fieldValue = \CRM_Utils_Array::value($customFieldName, $record, ''); // format the raw custom field value into proper display value - if ($fieldValue) { + if (isset($fieldValue)) { $fieldValue = \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID); } @@ -204,10 +206,10 @@ public function fill($format = NULL) { } if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/html'])) { - $this->tokenProcessor->rowValues[$this->tokenRow]['text/html'] = array(); + $this->tokenProcessor->rowValues[$this->tokenRow]['text/html'] = []; } if (!isset($this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'])) { - $this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'] = array(); + $this->tokenProcessor->rowValues[$this->tokenRow]['text/plain'] = []; } $htmlTokens = &$this->tokenProcessor->rowValues[$this->tokenRow]['text/html']; @@ -217,6 +219,7 @@ public function fill($format = NULL) { case 'text/html': // Plain => HTML. foreach ($textTokens as $entity => $values) { + $entityFields = civicrm_api3($entity, "getFields", ['api_action' => 'get']); foreach ($values as $field => $value) { if (!isset($htmlTokens[$entity][$field])) { // CRM-18420 - Activity Details Field are enclosed within

    , @@ -225,6 +228,10 @@ public function fill($format = NULL) { if ($entity == 'activity' && $field == 'details') { $htmlTokens[$entity][$field] = $value; } + elseif (\CRM_Utils_Array::value('data_type', \CRM_Utils_Array::value($field, $entityFields['values'])) == 'Memo') { + // Memo fields aka custom fields of type Note are html. + $htmlTokens[$entity][$field] = CRM_Utils_String::purifyHTML($value); + } else { $htmlTokens[$entity][$field] = htmlentities($value); } @@ -299,8 +306,7 @@ public function __construct($tokenProcessor, $tokenRow) { * @return bool */ public function offsetExists($offset) { - return - isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset]) + return isset($this->tokenProcessor->rowContexts[$this->tokenRow][$offset]) || isset($this->tokenProcessor->context[$offset]); } diff --git a/agpl-3.0.txt b/LICENSE similarity index 100% rename from agpl-3.0.txt rename to LICENSE diff --git a/README.md b/README.md index cf7462f47dba..ee36cd697c9d 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ Installation The download URLs and installation instructions are available on our website: https://civicrm.org/download -Detailed installation instructions are [on the wiki](https://wiki.civicrm.org/confluence/display/CRMDOC/Installation+and+Upgrades). +Detailed installation instructions can be found [in our sysadmin guide](https://docs.civicrm.org/sysadmin/en/latest/). Documentation ------------- -Documentation can be found at https://civicrm.org/documentation +Documentation can be found at https://docs.civicrm.org Support @@ -44,7 +44,7 @@ Development and Bugs Developers are highly encouraged to join [chat.civicrm.org](https://chat.civicrm.org) and post questions and ideas in the [Developer Discussion room](https://chat.civicrm.org/civicrm/channels/dev). -Installing the latest developmental code requires some [special steps](http://wiki.civicrm.org/confluence/display/CRMDOC/Contributing+to+CiviCRM+using+GitHub). +Installing the latest developmental code requires some [special steps](https://docs.civicrm.org/dev/en/latest/tools/git/). -Report all issues to CiviCRM via JIRA: -https://issues.civicrm.org +Report all issues to CiviCRM via GitLab: +https://lab.civicrm.org/ diff --git a/ang/angularFileUpload.ang.php b/ang/angularFileUpload.ang.php new file mode 100644 index 000000000000..68f4aa99aa1e --- /dev/null +++ b/ang/angularFileUpload.ang.php @@ -0,0 +1,9 @@ + 'civicrm', + 'js' => ['bower_components/angular-file-upload/angular-file-upload.min.js'], +]; diff --git a/ang/api4.ang.php b/ang/api4.ang.php new file mode 100644 index 000000000000..2c8a85b753be --- /dev/null +++ b/ang/api4.ang.php @@ -0,0 +1,13 @@ + 'civicrm', + 'js' => [ + 'ang/api4.js', + 'ang/api4/*.js', + 'ang/api4/*/*.js', + ], + 'css' => [], + 'partials' => [], + 'requires' => [], +]; diff --git a/ang/api4.js b/ang/api4.js new file mode 100644 index 000000000000..d1116fc45a06 --- /dev/null +++ b/ang/api4.js @@ -0,0 +1,4 @@ +(function(angular, $, _) { + // Declare a list of dependencies. + angular.module('api4', CRM.angRequires('api4')); +})(angular, CRM.$, CRM._); diff --git a/ang/api4/crmApi4.js b/ang/api4/crmApi4.js new file mode 100644 index 000000000000..743b35910bb3 --- /dev/null +++ b/ang/api4/crmApi4.js @@ -0,0 +1,37 @@ +(function(angular, $, _) { + + angular.module('api4').factory('crmApi4', function($q) { + var crmApi4 = function(entity, action, params, index) { + // JSON serialization in CRM.api4 is not aware of Angular metadata like $$hash, so use angular.toJson() + var deferred = $q.defer(); + var p; + var backend = crmApi4.backend || CRM.api4; + if (_.isObject(entity)) { + // eval content is locally generated. + /*jshint -W061 */ + p = backend(eval('('+angular.toJson(entity)+')'), action); + } else { + // eval content is locally generated. + /*jshint -W061 */ + p = backend(entity, action, eval('('+angular.toJson(params)+')'), index); + } + p.then( + function(result) { + deferred.resolve(result); + }, + function(error) { + deferred.reject(error); + } + ); + return deferred.promise; + }; + crmApi4.backend = null; + crmApi4.val = function(value) { + var d = $.Deferred(); + d.resolve(value); + return d.promise(); + }; + return crmApi4; + }); + +})(angular, CRM.$, CRM._); diff --git a/ang/api4Explorer.ang.php b/ang/api4Explorer.ang.php new file mode 100644 index 000000000000..9b974d53bf8d --- /dev/null +++ b/ang/api4Explorer.ang.php @@ -0,0 +1,17 @@ + 'civicrm', + 'js' => [ + 'ang/api4Explorer.js', + 'ang/api4Explorer/Explorer.js', + ], + 'css' => [ + 'css/api4-explorer.css', + ], + 'partials' => [ + 'ang/api4Explorer', + ], + 'basePages' => [], + 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'crmRouteBinder', 'ui.sortable', 'api4', 'ngSanitize'], +]; diff --git a/ang/api4Explorer.js b/ang/api4Explorer.js new file mode 100644 index 000000000000..85e10c467920 --- /dev/null +++ b/ang/api4Explorer.js @@ -0,0 +1,4 @@ +(function(angular, $, _) { + // Declare a list of dependencies. + angular.module('api4Explorer', CRM.angRequires('api4Explorer')); +})(angular, CRM.$, CRM._); diff --git a/ang/api4Explorer/Chain.html b/ang/api4Explorer/Chain.html new file mode 100644 index 000000000000..257efdeccffd --- /dev/null +++ b/ang/api4Explorer/Chain.html @@ -0,0 +1,4 @@ + + + + diff --git a/ang/api4Explorer/Explorer.html b/ang/api4Explorer/Explorer.html new file mode 100644 index 000000000000..bbfd35b95962 --- /dev/null +++ b/ang/api4Explorer/Explorer.html @@ -0,0 +1,152 @@ +

    +
    + +

    + {{ ts('CiviCRM API v4') }}{{ entity ? (' (' + entity + '::' + action + ')') : '' }} +

    + + +
    +

    + + {{ ts('Bootstrap theme not found.') }} +

    +

    {{ ts('This screen may not work correctly without a bootstrap-based theme such as Shoreditch installed.') }}

    +
    + +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + + +
    +
    + + +
    +
    +
    +
    + values * +
    + + +
    +
    + +
    +
    +
    + orderBy * +
    + + +
    +
    + +
    +
    +
    + chain +
    +
    +
    + +
    +
    +
    +
    +
    +
    +

    {{ helpTitle }}

    +
    +
    +

    {{ helpContent.description }}

    +
    +

    {{ text }}

    +
    +

    + {{ key }}: {{ item }} +

    +
    +
    +
    +
    +
    +
    +

    {{ ts('Code') }}

    +
    +
    + + + + + +
    {{ codeLabel[type] }}
    +
    +
    +
    +
    +

    + + + + + {{ ts('Result') }} +

    +
    +
    +
    
    +        
    +
    +
    + + +
    diff --git a/ang/api4Explorer/Explorer.js b/ang/api4Explorer/Explorer.js new file mode 100644 index 000000000000..3d596a2b18b3 --- /dev/null +++ b/ang/api4Explorer/Explorer.js @@ -0,0 +1,882 @@ +(function(angular, $, _, undefined) { + + // Schema metadata + var schema = CRM.vars.api4.schema; + // FK schema data + var links = CRM.vars.api4.links; + // Cache list of entities + var entities = []; + // Cache list of actions + var actions = []; + // Field options + var fieldOptions = {}; + + + angular.module('api4Explorer').config(function($routeProvider) { + $routeProvider.when('/explorer/:api4entity?/:api4action?', { + controller: 'Api4Explorer', + templateUrl: '~/api4Explorer/Explorer.html', + reloadOnSearch: false + }); + }); + + angular.module('api4Explorer').controller('Api4Explorer', function($scope, $routeParams, $location, $timeout, $http, crmUiHelp, crmApi4) { + var ts = $scope.ts = CRM.ts(); + $scope.entities = entities; + $scope.actions = actions; + $scope.fields = []; + $scope.fieldsAndJoins = []; + $scope.availableParams = {}; + $scope.params = {}; + $scope.index = ''; + var getMetaParams = {}, + objectParams = {orderBy: 'ASC', values: '', chain: ['Entity', '', '{}']}, + helpTitle = '', + helpContent = {}; + $scope.helpTitle = ''; + $scope.helpContent = {}; + $scope.entity = $routeParams.api4entity; + $scope.result = []; + $scope.status = 'default'; + $scope.loading = false; + $scope.controls = {}; + $scope.codeLabel = { + oop: ts('PHP (oop style)'), + php: ts('PHP (traditional)'), + js: ts('Javascript'), + cli: ts('Command Line') + }; + $scope.code = codeDefaults(); + + function codeDefaults() { + return _.mapValues($scope.codeLabel, function(val, key) { + return key === 'oop' ? ts('Select an entity and action') : ''; + }); + } + + if (!entities.length) { + formatForSelect2(schema, entities, 'name', ['description']); + } + + $scope.$bindToRoute({ + expr: 'index', + param: 'index', + default: '' + }); + + function ucfirst(str) { + return str[0].toUpperCase() + str.slice(1); + } + + function lcfirst(str) { + return str[0].toLowerCase() + str.slice(1); + } + + function pluralize(str) { + switch (str[str.length-1]) { + case 's': + return str + 'es'; + case 'y': + return str.slice(0, -1) + 'ies'; + default: + return str + 's'; + } + } + + // Turn a flat array into a select2 array + function arrayToSelect2(array) { + var out = []; + _.each(array, function(item) { + out.push({id: item, text: item}); + }); + return out; + } + + // Reformat an existing array of objects for compatibility with select2 + function formatForSelect2(input, container, key, extra, prefix) { + _.each(input, function(item) { + var id = (prefix || '') + item[key]; + var formatted = {id: id, text: id}; + if (extra) { + _.merge(formatted, _.pick(item, extra)); + } + container.push(formatted); + }); + return container; + } + + function getFieldList(source) { + var fields = [], + fieldInfo = _.findWhere(getEntity().actions, {name: $scope.action}).fields; + formatForSelect2(fieldInfo, fields, 'name', ['description', 'required', 'default_value']); + return fields; + } + + function addJoins(fieldList) { + var fields = _.cloneDeep(fieldList), + fks = _.findWhere(links, {entity: $scope.entity}) || {}; + _.each(fks.links, function(link) { + var linkFields = entityFields(link.entity); + if (linkFields) { + fields.push({ + text: link.alias, + description: 'Join to ' + link.entity, + children: formatForSelect2(linkFields, [], 'name', ['description'], link.alias + '.') + }); + } + }); + return fields; + } + + $scope.help = function(title, param) { + if (!param) { + $scope.helpTitle = helpTitle; + $scope.helpContent = helpContent; + } else { + $scope.helpTitle = title; + $scope.helpContent = param; + } + }; + + $scope.fieldHelp = function(fieldName) { + var field = getField(fieldName, $scope.entity, $scope.action); + if (!field) { + return; + } + var info = { + description: field.description, + type: field.data_type + }; + if (field.default_value) { + info.default = field.default_value; + } + if (field.required_if) { + info.required_if = field.required_if; + } else if (field.required) { + info.required = 'true'; + } + return info; + }; + + $scope.valuesFields = function() { + var fields = _.cloneDeep($scope.fields); + // Disable fields that are already in use + _.each($scope.params.values || [], function(val) { + (_.findWhere(fields, {id: val[0]}) || {}).disabled = true; + }); + return {results: fields}; + }; + + $scope.formatSelect2Item = function(row) { + return _.escape(row.text) + + (row.required ? ' *' : '') + + (row.description ? '

    ' + _.escape(row.description) + '

    ' : ''); + }; + + $scope.clearParam = function(name) { + $scope.params[name] = $scope.availableParams[name].default; + }; + + $scope.isSpecial = function(name) { + var specialParams = ['select', 'fields', 'action', 'where', 'values', 'orderBy', 'chain']; + return _.contains(specialParams, name); + }; + + $scope.selectRowCount = function() { + if ($scope.isSelectRowCount()) { + $scope.params.select = []; + } else { + $scope.params.select = ['row_count']; + if ($scope.params.limit == 25) { + $scope.params.limit = 0; + } + } + }; + + $scope.isSelectRowCount = function() { + return $scope.params && $scope.params.select && $scope.params.select.length === 1 && $scope.params.select[0] === 'row_count'; + }; + + function getEntity(entityName) { + return _.findWhere(schema, {name: entityName || $scope.entity}); + } + + // Get all params that have been set + function getParams() { + var params = {}; + _.each($scope.params, function(param, key) { + if (param != $scope.availableParams[key].default && !(typeof param === 'object' && _.isEmpty(param))) { + if (_.contains($scope.availableParams[key].type, 'array') && (typeof objectParams[key] === 'undefined')) { + params[key] = parseYaml(_.cloneDeep(param)); + } else { + params[key] = param; + } + } + }); + _.each(objectParams, function(defaultVal, key) { + if (params[key]) { + var newParam = {}; + _.each(params[key], function(item) { + var val = _.cloneDeep(item[1]); + // Remove blank items from "chain" array + if (_.isArray(val)) { + _.eachRight(item[1], function(v, k) { + if (v) { + return false; + } + val.length--; + }); + } + newParam[item[0]] = parseYaml(val); + }); + params[key] = newParam; + } + }); + return params; + } + + function parseYaml(input) { + if (typeof input === 'undefined') { + return undefined; + } + if (_.isObject(input) || _.isArray(input)) { + _.each(input, function(item, index) { + input[index] = parseYaml(item); + }); + return input; + } + try { + var output = (input === '>') ? '>' : jsyaml.safeLoad(input); + // We don't want dates parsed to js objects + return _.isDate(output) ? input : output; + } catch (e) { + return input; + } + } + + function selectAction() { + $scope.action = $routeParams.api4action; + $scope.fieldsAndJoins = []; + if (!actions.length) { + formatForSelect2(getEntity().actions, actions, 'name', ['description', 'params']); + } + if ($scope.action) { + var actionInfo = _.findWhere(actions, {id: $scope.action}); + $scope.fields = getFieldList(); + if (_.contains(['get', 'update', 'delete', 'replace'], $scope.action)) { + $scope.fieldsAndJoins = addJoins($scope.fields); + } else { + $scope.fieldsAndJoins = $scope.fields; + } + _.each(actionInfo.params, function (param, name) { + var format, + defaultVal = _.cloneDeep(param.default); + if (param.type) { + switch (param.type[0]) { + case 'int': + case 'bool': + format = param.type[0]; + break; + + case 'array': + case 'object': + format = 'json'; + break; + + default: + format = 'raw'; + } + if (name == 'limit') { + defaultVal = 25; + } + if (name === 'values') { + defaultVal = defaultValues(defaultVal); + } + $scope.$bindToRoute({ + expr: 'params["' + name + '"]', + param: name, + format: format, + default: defaultVal, + deep: format === 'json' + }); + } + if (typeof objectParams[name] !== 'undefined') { + $scope.$watch('params.' + name, function(values) { + // Remove empty values + _.each(values, function(clause, index) { + if (!clause || !clause[0]) { + $scope.params[name].splice(index, 1); + } + }); + }, true); + $scope.$watch('controls.' + name, function(value) { + var field = value; + $timeout(function() { + if (field) { + var defaultOp = _.cloneDeep(objectParams[name]); + if (name === 'chain') { + var num = $scope.params.chain.length; + defaultOp[0] = field; + field = 'name_me_' + num; + } + $scope.params[name].push([field, defaultOp]); + $scope.controls[name] = null; + } + }); + }); + } + }); + $scope.availableParams = actionInfo.params; + } + writeCode(); + } + + function defaultValues(defaultVal) { + _.each($scope.fields, function(field) { + if (field.required) { + defaultVal.push([field.id, '']); + } + }); + return defaultVal; + } + + function stringify(value, trim) { + if (typeof value === 'undefined') { + return ''; + } + var str = JSON.stringify(value).replace(/,/g, ', '); + if (trim) { + str = str.slice(1, -1); + } + return str.trim(); + } + + function writeCode() { + var code = codeDefaults(), + entity = $scope.entity, + action = $scope.action, + params = getParams(), + index = isInt($scope.index) ? +$scope.index : $scope.index, + result = 'result'; + if ($scope.entity && $scope.action) { + if (action.slice(0, 3) === 'get') { + result = entity.substr(0, 7) === 'Custom_' ? _.camelCase(entity.substr(7)) : entity; + result = lcfirst(action.replace(/s$/, '').slice(3) || result); + } + var results = lcfirst(_.isNumber(index) ? result : pluralize(result)), + paramCount = _.size(params), + isSelectRowCount = params.select && params.select.length === 1 && params.select[0] === 'row_count', + i = 0; + + if (isSelectRowCount) { + results = result + 'Count'; + } + + // Write javascript + code.js = "CRM.api4('" + entity + "', '" + action + "', {"; + _.each(params, function(param, key) { + code.js += "\n " + key + ': ' + stringify(param) + + (++i < paramCount ? ',' : ''); + if (key === 'checkPermissions') { + code.js += ' // IGNORED: permissions are always enforced from client-side requests'; + } + }); + code.js += "\n}"; + if (index || index === 0) { + code.js += ', ' + JSON.stringify(index); + } + code.js += ").then(function(" + results + ") {\n // do something with " + results + " array\n}, function(failure) {\n // handle failure\n});"; + + // Write php code + code.php = '$' + results + " = civicrm_api4('" + entity + "', '" + action + "', ["; + _.each(params, function(param, key) { + code.php += "\n '" + key + "' => " + phpFormat(param, 4) + ','; + }); + code.php += "\n]"; + if (index || index === 0) { + code.php += ', ' + phpFormat(index); + } + code.php += ");"; + + // Write oop code + if (entity.substr(0, 7) !== 'Custom_') { + code.oop = '$' + results + " = \\Civi\\Api4\\" + entity + '::' + action + '()'; + } else { + code.oop = '$' + results + " = \\Civi\\Api4\\CustomValue::" + action + "('" + entity.substr(7) + "')"; + } + _.each(params, function(param, key) { + var val = ''; + if (typeof objectParams[key] !== 'undefined' && key !== 'chain') { + _.each(param, function(item, index) { + val = phpFormat(index) + ', ' + phpFormat(item, 4); + code.oop += "\n ->add" + ucfirst(key).replace(/s$/, '') + '(' + val + ')'; + }); + } else if (key === 'where') { + _.each(param, function (clause) { + if (clause[0] === 'AND' || clause[0] === 'OR' || clause[0] === 'NOT') { + code.oop += "\n ->addClause(" + phpFormat(clause[0]) + ", " + phpFormat(clause[1]).slice(1, -1) + ')'; + } else { + code.oop += "\n ->addWhere(" + phpFormat(clause).slice(1, -1) + ")"; + } + }); + } else if (key === 'select' && isSelectRowCount) { + code.oop += "\n ->selectRowCount()"; + } else { + code.oop += "\n ->set" + ucfirst(key) + '(' + phpFormat(param, 4) + ')'; + } + }); + code.oop += "\n ->execute()"; + if (_.isNumber(index)) { + code.oop += !index ? '\n ->first()' : (index === -1 ? '\n ->last()' : '\n ->itemAt(' + index + ')'); + } else if (index) { + code.oop += "\n ->indexBy('" + index + "')"; + } else if (isSelectRowCount) { + code.oop += "\n ->count()"; + } + code.oop += ";\n"; + if (!_.isNumber(index) && !isSelectRowCount) { + code.oop += "foreach ($" + results + ' as $' + ((_.isString(index) && index) ? index + ' => $' : '') + result + ') {\n // do something\n}'; + } + + // Write cli code + code.cli = 'cv api4 ' + entity + '.' + action + " '" + stringify(params) + "'"; + } + _.each(code, function(val, type) { + $scope.code[type] = prettyPrintOne(val); + }); + } + + function isInt(value) { + if (_.isFinite(value)) { + return true; + } + if (!_.isString(value)) { + return false; + } + return /^-{0,1}\d+$/.test(value); + } + + function formatMeta(resp) { + var ret = ''; + _.each(resp, function(val, key) { + if (key !== 'values' && !_.isPlainObject(val) && !_.isFunction(val)) { + ret += (ret.length ? ', ' : '') + key + ': ' + (_.isArray(val) ? '[' + val + ']' : val); + } + }); + return prettyPrintOne(ret); + } + + $scope.execute = function() { + $scope.status = 'warning'; + $scope.loading = true; + $http.get(CRM.url('civicrm/ajax/api4/' + $scope.entity + '/' + $scope.action, { + params: angular.toJson(getParams()), + index: $scope.index + })).then(function(resp) { + $scope.loading = false; + $scope.status = 'success'; + $scope.result = [formatMeta(resp.data), prettyPrintOne(JSON.stringify(resp.data.values, null, 2), 'js', 1)]; + }, function(resp) { + $scope.loading = false; + $scope.status = 'danger'; + $scope.result = [formatMeta(resp), prettyPrintOne(JSON.stringify(resp.data, null, 2))]; + }); + }; + + /** + * Format value to look like php code + */ + function phpFormat(val, indent) { + if (typeof val === 'undefined') { + return ''; + } + indent = (typeof indent === 'number') ? _.repeat(' ', indent) : (indent || ''); + var ret = '', + baseLine = indent ? indent.slice(0, -2) : '', + newLine = indent ? '\n' : '', + trailingComma = indent ? ',' : ''; + if ($.isPlainObject(val)) { + $.each(val, function(k, v) { + ret += (ret ? ', ' : '') + newLine + indent + "'" + k + "' => " + phpFormat(v); + }); + return '[' + ret + trailingComma + newLine + baseLine + ']'; + } + if ($.isArray(val)) { + $.each(val, function(k, v) { + ret += (ret ? ', ' : '') + newLine + indent + phpFormat(v); + }); + return '[' + ret + trailingComma + newLine + baseLine + ']'; + } + if (_.isString(val) && !_.contains(val, "'")) { + return "'" + val + "'"; + } + return JSON.stringify(val).replace(/\$/g, '\\$'); + } + + function fetchMeta() { + crmApi4(getMetaParams) + .then(function(data) { + if (data.actions) { + getEntity().actions = data.actions; + selectAction(); + } + }); + } + + // Help for an entity with no action selected + function showEntityHelp(entityName) { + var entityInfo = getEntity(entityName); + $scope.helpTitle = helpTitle = $scope.entity; + $scope.helpContent = helpContent = { + description: entityInfo.description, + comment: entityInfo.comment + }; + } + + if (!$scope.entity) { + $scope.helpTitle = helpTitle = ts('Help'); + $scope.helpContent = helpContent = {description: ts('Welcome to the api explorer.'), comment: ts('Select an entity to begin.')}; + } else if (!actions.length && !getEntity().actions) { + getMetaParams.actions = [$scope.entity, 'getActions', {chain: {fields: [$scope.entity, 'getFields', {action: '$name'}]}}]; + fetchMeta(); + } else { + selectAction(); + } + + if ($scope.entity) { + showEntityHelp($scope.entity); + } + + // Update route when changing entity + $scope.$watch('entity', function(newVal, oldVal) { + if (oldVal !== newVal) { + // Flush actions cache to re-fetch for new entity + actions = []; + $location.url('/explorer/' + newVal); + } + }); + + // Update route when changing actions + $scope.$watch('action', function(newVal, oldVal) { + if ($scope.entity && $routeParams.api4action !== newVal && !_.isUndefined(newVal)) { + $location.url('/explorer/' + $scope.entity + '/' + newVal); + } else if (newVal) { + $scope.helpTitle = helpTitle = $scope.entity + '::' + newVal; + $scope.helpContent = helpContent = _.pick(_.findWhere(getEntity().actions, {name: newVal}), ['description', 'comment']); + } + }); + + $scope.indexHelp = { + description: ts('(string|int) Index results or select by index.'), + comment: ts('Pass a string to index the results by a field value. E.g. index: "name" will return an associative array with names as keys.') + '\n\n' + + ts('Pass an integer to return a single result; e.g. index: 0 will return the first result, 1 will return the second, and -1 will return the last.') + }; + + $scope.$watch('params', writeCode, true); + $scope.$watch('index', writeCode); + writeCode(); + + }); + + angular.module('api4Explorer').directive('crmApi4WhereClause', function($timeout) { + return { + scope: { + data: '=crmApi4WhereClause' + }, + templateUrl: '~/api4Explorer/WhereClause.html', + link: function (scope, element, attrs) { + var ts = scope.ts = CRM.ts(); + scope.newClause = ''; + scope.conjunctions = ['AND', 'OR', 'NOT']; + scope.operators = CRM.vars.api4.operators; + + scope.addGroup = function(op) { + scope.data.where.push([op, []]); + }; + + scope.removeGroup = function() { + scope.data.groupParent.splice(scope.data.groupIndex, 1); + }; + + scope.onSort = function(event, ui) { + $('.api4-where-fieldset').toggleClass('api4-sorting', event.type === 'sortstart'); + $('.api4-input.form-inline').css('margin-left', ''); + }; + + // Indent clause while dragging between nested groups + scope.onSortOver = function(event, ui) { + var offset = 0; + if (ui.sender) { + offset = $(ui.placeholder).offset().left - $(ui.sender).offset().left; + } + $('.api4-input.form-inline.ui-sortable-helper').css('margin-left', '' + offset + 'px'); + }; + + scope.$watch('newClause', function(value) { + var field = value; + $timeout(function() { + if (field) { + scope.data.where.push([field, '=', '']); + scope.newClause = null; + } + }); + }); + scope.$watch('data.where', function(values) { + // Remove empty values + _.each(values, function(clause, index) { + if (typeof clause !== 'undefined' && !clause[0]) { + values.splice(index, 1); + } + }); + }, true); + } + }; + }); + + angular.module('api4Explorer').directive('api4ExpValue', function($routeParams, crmApi4) { + return { + scope: { + data: '=api4ExpValue' + }, + require: 'ngModel', + link: function (scope, element, attrs, ctrl) { + var ts = scope.ts = CRM.ts(), + multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], scope.data.op), + entity = $routeParams.api4entity, + action = $routeParams.api4action; + + function destroyWidget() { + var $el = $(element); + if ($el.is('.crm-form-date-wrapper .crm-hidden-date')) { + $el.crmDatepicker('destroy'); + } + if ($el.is('.select2-container + input')) { + $el.crmEntityRef('destroy'); + } + $(element).removeData().removeAttr('type').removeAttr('placeholder').show(); + } + + function makeWidget(field, op) { + var $el = $(element), + inputType = field.input_type; + dataType = field.data_type; + if (!op) { + op = field.serialize || dataType === 'Array' ? 'IN' : '='; + } + multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], op); + if (op === 'IS NULL' || op === 'IS NOT NULL') { + $el.hide(); + return; + } + if (inputType === 'Date') { + if (_.includes(['=', '!=', '<>', '<', '>=', '<', '<='], op)) { + $el.crmDatepicker({time: (field.input_attrs && field.input_attrs.time) || false}); + } + } else if (_.includes(['=', '!=', '<>', 'IN', 'NOT IN'], op) && (field.fk_entity || field.options || dataType === 'Boolean')) { + if (field.fk_entity) { + $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}}); + } else if (field.options) { + $el.addClass('loading').attr('placeholder', ts('- select -')).crmSelect2({multiple: multi, data: [{id: '', text: ''}]}); + loadFieldOptions(field.entity || entity).then(function(data) { + var options = []; + _.each(_.findWhere(data, {name: field.name}).options, function(val, key) { + options.push({id: key, text: val}); + }); + $el.removeClass('loading').select2({data: options, multiple: multi}); + }); + } else if (dataType === 'Boolean') { + $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [ + {id: '1', text: ts('Yes')}, + {id: '0', text: ts('No')} + ]}); + } + } else if (dataType === 'Integer' && !multi) { + $el.attr('type', 'number'); + } + } + + function loadFieldOptions(entity) { + if (!fieldOptions[entity + action]) { + fieldOptions[entity + action] = crmApi4(entity, 'getFields', { + loadOptions: true, + action: action, + where: [["options", "!=", false]], + select: ["name", "options"] + }); + } + return fieldOptions[entity + action]; + } + + // Copied from ng-list but applied conditionally if field is multi-valued + var parseList = function(viewValue) { + // If the viewValue is invalid (say required but empty) it will be `undefined` + if (_.isUndefined(viewValue)) return; + + if (!multi) { + return viewValue; + } + + var list = []; + + if (viewValue) { + _.each(viewValue.split(','), function(value) { + if (value) list.push(_.trim(value)); + }); + } + + return list; + }; + + // Copied from ng-list + ctrl.$parsers.push(parseList); + ctrl.$formatters.push(function(value) { + return _.isArray(value) ? value.join(', ') : value; + }); + + // Copied from ng-list + ctrl.$isEmpty = function(value) { + return !value || !value.length; + }; + + scope.$watchCollection('data', function(data) { + destroyWidget(); + var field = getField(data.field, entity, action); + if (field) { + makeWidget(field, data.op); + } + }); + } + }; + }); + + + angular.module('api4Explorer').directive('api4ExpChain', function(crmApi4) { + return { + scope: { + chain: '=api4ExpChain', + mainEntity: '=', + entities: '=' + }, + templateUrl: '~/api4Explorer/Chain.html', + link: function (scope, element, attrs) { + var ts = scope.ts = CRM.ts(); + + function changeEntity(newEntity, oldEntity) { + // When clearing entity remove this chain + if (!newEntity) { + scope.chain[0] = ''; + return; + } + // Reset action && index + if (newEntity !== oldEntity) { + scope.chain[1][1] = scope.chain[1][2] = ''; + } + if (getEntity(newEntity).actions) { + setActions(); + } else { + crmApi4(newEntity, 'getActions', {chain: {fields: [newEntity, 'getFields', {action: '$name'}]}}) + .then(function(data) { + getEntity(data.entity).actions = data; + if (data.entity === scope.chain[1][0]) { + setActions(); + } + }); + } + } + + function setActions() { + scope.actions = [''].concat(_.pluck(getEntity(scope.chain[1][0]).actions, 'name')); + } + + // Set default params when choosing action + function changeAction(newAction, oldAction) { + var link; + // Prepopulate links + if (newAction && newAction !== oldAction) { + // Clear index + scope.chain[1][3] = ''; + // Look for links back to main entity + _.each(entityFields(scope.chain[1][0]), function(field) { + if (field.fk_entity === scope.mainEntity) { + link = [field.name, '$id']; + } + }); + // Look for links from main entity + if (!link && newAction !== 'create') { + _.each(entityFields(scope.mainEntity), function(field) { + if (field.fk_entity === scope.chain[1][0]) { + link = ['id', '$' + field.name]; + // Since we're specifying the id, set index to getsingle + scope.chain[1][3] = '0'; + } + }); + } + if (link && _.contains(['get', 'update', 'replace', 'delete'], newAction)) { + scope.chain[1][2] = '{where: [[' + link[0] + ', =, ' + link[1] + ']]}'; + } + else if (link && _.contains(['create'], newAction)) { + scope.chain[1][2] = '{values: {' + link[0] + ': ' + link[1] + '}}'; + } + else if (link && _.contains(['save'], newAction)) { + scope.chain[1][2] = '{records: [{' + link[0] + ': ' + link[1] + '}]}'; + } else { + scope.chain[1][2] = '{}'; + } + } + } + + scope.$watch("chain[1][0]", changeEntity); + scope.$watch("chain[1][1]", changeAction); + } + }; + }); + + function getEntity(entityName) { + return _.findWhere(schema, {name: entityName}); + } + + function entityFields(entityName, action) { + var entity = getEntity(entityName); + if (entity && action && entity.actions) { + return _.findWhere(entity.actions, {name: action}).fields; + } + return _.result(entity, 'fields'); + } + + function getField(fieldName, entity, action) { + var fieldNames = fieldName.split('.'); + return get(entity, fieldNames); + + function get(entity, fieldNames) { + if (fieldNames.length === 1) { + return _.findWhere(entityFields(entity, action), {name: fieldNames[0]}); + } + var comboName = _.findWhere(entityFields(entity, action), {name: fieldNames[0] + '.' + fieldNames[1]}); + if (comboName) { + return comboName; + } + var linkName = fieldNames.shift(), + entityLinks = _.findWhere(links, {entity: entity}).links, + newEntity = _.findWhere(entityLinks, {alias: linkName}).entity; + return get(newEntity, fieldNames); + } + } + + // Collapsible optgroups for select2 + $(function() { + $('body') + .on('select2-open', function(e) { + if ($(e.target).hasClass('collapsible-optgroups')) { + $('#select2-drop') + .off('.collapseOptionGroup') + .addClass('collapsible-optgroups-enabled') + .on('click.collapseOptionGroup', '.select2-result-with-children > .select2-result-label', function() { + $(this).parent().toggleClass('optgroup-expanded'); + }); + } + }) + .on('select2-close', function() { + $('#select2-drop').off('.collapseOptionGroup').removeClass('collapsible-optgroups-enabled'); + }); + }); +})(angular, CRM.$, CRM._); diff --git a/ang/api4Explorer/WhereClause.html b/ang/api4Explorer/WhereClause.html new file mode 100644 index 000000000000..d36480f9de54 --- /dev/null +++ b/ang/api4Explorer/WhereClause.html @@ -0,0 +1,39 @@ +{{ data.label || data.op + ' group' }} * +
    + +
    +
    +
    +
    + + Where + {{ data.op }} + + +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + +
    \ No newline at end of file diff --git a/ang/checklist-model.ang.php b/ang/checklist-model.ang.php new file mode 100644 index 000000000000..ffa8613bda5d --- /dev/null +++ b/ang/checklist-model.ang.php @@ -0,0 +1,10 @@ + 'civicrm', + 'basePages' => [], + 'js' => ['bower_components/checklist-model/checklist-model.js'], +]; diff --git a/ang/crmApp.ang.php b/ang/crmApp.ang.php new file mode 100644 index 000000000000..d4179315bd05 --- /dev/null +++ b/ang/crmApp.ang.php @@ -0,0 +1,9 @@ + 'civicrm', + 'js' => ['ang/crmApp.js'], +]; diff --git a/ang/crmApp.js b/ang/crmApp.js index 0726a045040c..c7bb81e29bc9 100644 --- a/ang/crmApp.js +++ b/ang/crmApp.js @@ -5,6 +5,16 @@ var crmApp = angular.module('crmApp', CRM.angular.modules); crmApp.config(['$routeProvider', function($routeProvider) { + + if (CRM.crmApp.defaultRoute) { + $routeProvider.when('/', { + template: '
    ', + controller: function($location) { + $location.path(CRM.crmApp.defaultRoute); + } + }); + } + $routeProvider.otherwise({ template: ts('Unknown path') }); diff --git a/ang/crmAttachment.ang.php b/ang/crmAttachment.ang.php new file mode 100644 index 000000000000..e278f75e32c7 --- /dev/null +++ b/ang/crmAttachment.ang.php @@ -0,0 +1,15 @@ + 'civicrm', + 'js' => ['ang/crmAttachment.js'], + 'css' => ['ang/crmAttachment.css'], + 'partials' => ['ang/crmAttachment'], + 'settings' => [ + 'token' => \CRM_Core_Page_AJAX_Attachment::createToken(), + ], + 'requires' => ['angularFileUpload', 'crmResource'], +]; diff --git a/ang/crmAttachment.js b/ang/crmAttachment.js index 35dd1c582fe2..b89fe4ca22ef 100644 --- a/ang/crmAttachment.js +++ b/ang/crmAttachment.js @@ -1,7 +1,7 @@ /// crmFile: Manage file attachments (function (angular, $, _) { - angular.module('crmAttachment', ['angularFileUpload']); + angular.module('crmAttachment', CRM.angRequires('crmAttachment')); // crmAttachment manages the list of files which are attached to a given entity angular.module('crmAttachment').factory('CrmAttachments', function (crmApi, crmStatus, FileUploader, $q) { diff --git a/ang/crmAutosave.ang.php b/ang/crmAutosave.ang.php new file mode 100644 index 000000000000..6f998782c709 --- /dev/null +++ b/ang/crmAutosave.ang.php @@ -0,0 +1,10 @@ + 'civicrm', + 'js' => ['ang/crmAutosave.js'], + 'requires' => ['crmUtil'], +]; diff --git a/ang/crmAutosave.js b/ang/crmAutosave.js index a697880b8f11..359ccfebac89 100644 --- a/ang/crmAutosave.js +++ b/ang/crmAutosave.js @@ -1,7 +1,7 @@ /// crmAutosave (function(angular, $, _) { - angular.module('crmAutosave', ['crmUtil']); + angular.module('crmAutosave', CRM.angRequires('crmAutosave')); // usage: // var autosave = new CrmAutosaveCtrl({ diff --git a/ang/crmCaseType.ang.php b/ang/crmCaseType.ang.php new file mode 100644 index 000000000000..c71e9c6678b5 --- /dev/null +++ b/ang/crmCaseType.ang.php @@ -0,0 +1,14 @@ + 'civicrm', + 'js' => ['ang/crmCaseType.js'], + 'css' => ['ang/crmCaseType.css'], + 'partials' => ['ang/crmCaseType'], + 'requires' => ['ngRoute', 'ui.utils', 'crmUi', 'unsavedChanges', 'crmUtil', 'crmResource'], +]; diff --git a/ang/crmCaseType.css b/ang/crmCaseType.css index d5fa1d622087..6352b3d24dc8 100644 --- a/ang/crmCaseType.css +++ b/ang/crmCaseType.css @@ -2,8 +2,14 @@ vertical-align: middle; cursor: move; } + +.crmCaseType .fa-pencil { + margin: 0.2em 0.2em 0 0; + cursor: pointer; +} + .crmCaseType .fa-trash { - margin: 0.4em 0.2em 0 0; + margin: 0.56em 0.2em 0 0; cursor: pointer; } diff --git a/ang/crmCaseType.js b/ang/crmCaseType.js index f4e732bd6dd8..0b9743223772 100644 --- a/ang/crmCaseType.js +++ b/ang/crmCaseType.js @@ -1,6 +1,6 @@ (function(angular, $, _) { - var crmCaseType = angular.module('crmCaseType', ['ngRoute', 'ui.utils', 'crmUi', 'unsavedChanges', 'crmUtil']); + var crmCaseType = angular.module('crmCaseType', CRM.angRequires('crmCaseType')); // Note: This template will be passed to cloneDeep(), so don't put any funny stuff in here! var newCaseTypeTemplate = { @@ -57,7 +57,10 @@ reqs.caseStatuses = ['OptionValue', 'get', { option_group_id: 'case_status', sequential: 1, - options: {limit: 0} + options: { + sort: 'weight', + limit: 0 + } }]; reqs.actTypes = ['OptionValue', 'get', { option_group_id: 'activity_type', @@ -67,10 +70,17 @@ limit: 0 } }]; + reqs.defaultAssigneeTypes = ['OptionValue', 'get', { + option_group_id: 'activity_default_assignee', + sequential: 1, + options: { + limit: 0 + } + }]; reqs.relTypes = ['RelationshipType', 'get', { sequential: 1, options: { - sort: CRM.crmCaseType.REL_TYPE_CNAME, + sort: 'label_a_b', limit: 0 } }]; @@ -103,7 +113,9 @@ }; $(input).crmSelect2({ - data: scope[attrs.crmOptions], + data: function () { + return { results: scope[attrs.crmOptions] }; + }, createSearchChoice: function(term) { return {id: term, text: term + ' (' + ts('new') + ')'}; }, @@ -116,51 +128,279 @@ scope.$evalAsync('_resetSelection()'); e.preventDefault(); }); - - scope.$watch(attrs.crmOptions, function(value) { - $(input).select2('data', scope[attrs.crmOptions]); - $(input).select2('val', ''); - }); } }; }); - crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls) { - // CRM_Case_XMLProcessor::REL_TYPE_CNAME - var REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME, + crmCaseType.directive('crmEditableTabTitle', function($timeout) { + return { + restrict: 'AE', + link: function(scope, element, attrs) { + element.addClass('crm-editable crm-editable-enabled'); + var titleLabel = $(element).find('span'); + var penIcon = $('').prependTo(element); + var saveButton = $('').appendTo(element); + var cancelButton = $('').appendTo(element); + $('button', element).wrapAll('
    diff --git a/ang/crmCaseType/edit.html b/ang/crmCaseType/edit.html index a31d9c5e5efa..55c7faf4fc54 100644 --- a/ang/crmCaseType/edit.html +++ b/ang/crmCaseType/edit.html @@ -3,19 +3,13 @@ Required vars: caseType -->

    {{caseType.title || ts('New Case Type')}}

    + +
    + {{ts('Use this screen to define or update the Case Roles, Activity Types, and Timelines for a case type.')}} {{ts('Learn more...')}} +
    +
    -
    - {{ts('Use this screen to define or update the Case Roles, Activity Types, and Timelines for a case type.')}} {{ts('Learn more...')}} -
    -
    - - -
    @@ -25,10 +19,14 @@

    {{caseType.title || ts('New Case Type')}}

  • {{ts('Case Statuses')}}
  • {{ts('Activity Types')}}
  • - {{ activitySet.label }} + +
    + {{ activitySet.label }} +
    +
    {{ts('Remove')}} + ng-click="removeItem(caseType.definition.activitySets, activitySet)"> - - - {{ts('Edit')}} +
    - - {{ts('more')}} - + + + + + + + + + + + + + + + + + + - - -
    {{ts('Title')}}{{ts('Name')}}{{ts('Description')}}{{ts('Enabled?')}}
    {{caseType.title}}{{caseType.name}}{{caseType.description}}{{caseType.is_active == 1 ? ts('Yes') : ts('No')}} + + {{ts('Edit')}} + + + {{ts('more')}} + + - -
    + + + + - diff --git a/ang/crmCaseType/rolesTable.html b/ang/crmCaseType/rolesTable.html index 94fd7993c614..8af3b488c4f9 100644 --- a/ang/crmCaseType/rolesTable.html +++ b/ang/crmCaseType/rolesTable.html @@ -5,17 +5,18 @@ - + - - - - + + + + + @@ -30,7 +31,7 @@ crm-var="newRole" crm-on-add="addRole(caseType.definition.caseRoles, newRole)" placeholder="{{ts('Add role')}}" - /> + > diff --git a/ang/crmCaseType/sequenceTable.html b/ang/crmCaseType/sequenceTable.html index 056c60c8b817..e07a11bde88c 100644 --- a/ang/crmCaseType/sequenceTable.html +++ b/ang/crmCaseType/sequenceTable.html @@ -34,7 +34,7 @@ crm-var="newActivity" crm-on-add="addActivity(activitySet, newActivity)" placeholder="{{ts('Add activity')}}" - /> + > diff --git a/ang/crmCaseType/timelineTable.html b/ang/crmCaseType/timelineTable.html index c1ad99adaea8..fd24b03bfb3b 100644 --- a/ang/crmCaseType/timelineTable.html +++ b/ang/crmCaseType/timelineTable.html @@ -11,6 +11,7 @@ + @@ -21,13 +22,13 @@ + - diff --git a/ang/crmCxn.ang.php b/ang/crmCxn.ang.php new file mode 100644 index 000000000000..ff267991f832 --- /dev/null +++ b/ang/crmCxn.ang.php @@ -0,0 +1,12 @@ + 'civicrm', + 'js' => ['ang/crmCxn.js', 'ang/crmCxn/*.js'], + 'css' => ['ang/crmCxn.css'], + 'partials' => ['ang/crmCxn'], + 'requires' => ['crmUtil', 'ngRoute', 'ngSanitize', 'ui.utils', 'crmUi', 'dialogService', 'crmResource'], +]; diff --git a/ang/crmCxn.js b/ang/crmCxn.js index c2fa6ec28785..244d038ffc81 100644 --- a/ang/crmCxn.js +++ b/ang/crmCxn.js @@ -1,8 +1,6 @@ (function (angular, $, _) { - angular.module('crmCxn', [ - 'crmUtil', 'ngRoute', 'ngSanitize', 'ui.utils', 'crmUi', 'dialogService' - ]); + angular.module('crmCxn', CRM.angRequires('crmCxn')); angular.module('crmCxn').config([ '$routeProvider', diff --git a/ang/crmCxn/ManageCtrl.html b/ang/crmCxn/ManageCtrl.html index 2fe2f173c3b0..891942e69ba2 100644 --- a/ang/crmCxn/ManageCtrl.html +++ b/ang/crmCxn/ManageCtrl.html @@ -84,6 +84,7 @@

    {{ts('Existing Connections')}}

    +

    {{ts('New Connections')}}

    {{ts('Name')}}{{ts('Display Label')}} {{ts('Assign to Creator')}} {{ts('Is Manager')}}
    {{relType.name}}
    {{relType.displayLabel}}
    {{ts('Reference')}} {{ts('Offset')}} {{ts('Select')}}{{ts('Default assignee')}}
    - - {{ activity.label }} + + {{activity.label}} @@ -51,21 +51,46 @@ type="text" ng-pattern="/^-?[0-9]*$/" ng-model="activity.reference_offset" - ng-hide="activity.name == 'Open Case'" - ng-required="activity.name != 'Open Case'" - /> + > + + +

    + +

    + +

    + +

    +
    - + + >
    @@ -111,6 +112,8 @@

    {{ts('New Connections')}}

    +
  • +
    {{ts('No available applications')}} diff --git a/ang/crmCxn/PermTable.html b/ang/crmCxn/PermTable.html index 08c35ff847b6..d2a1eabfed40 100644 --- a/ang/crmCxn/PermTable.html +++ b/ang/crmCxn/PermTable.html @@ -21,7 +21,7 @@ {{ts('Any')}} {{api.actions}} - + {{action}},
    diff --git a/ang/crmD3.ang.php b/ang/crmD3.ang.php new file mode 100644 index 000000000000..0a737341ae81 --- /dev/null +++ b/ang/crmD3.ang.php @@ -0,0 +1,16 @@ + 'civicrm', + 'js' => [ + 'ang/crmD3.js', + 'bower_components/d3/d3.min.js', + ], + 'requires' => [], +]; diff --git a/ang/crmD3.js b/ang/crmD3.js index eca29a3cfb4b..d06eeac9b40b 100644 --- a/ang/crmD3.js +++ b/ang/crmD3.js @@ -1,4 +1,3 @@ (function (angular, $, _) { - // thin stub for declaring dependencies - angular.module('crmD3', []); + angular.module('crmD3', CRM.angRequires('crmD3')); })(angular, CRM.$, CRM._); diff --git a/ang/crmExample.ang.php b/ang/crmExample.ang.php new file mode 100644 index 000000000000..763f1eaca2ab --- /dev/null +++ b/ang/crmExample.ang.php @@ -0,0 +1,11 @@ + 'civicrm', + 'js' => ['ang/crmExample.js'], + 'partials' => ['ang/crmExample'], + 'requires' => ['crmUtil', 'ngRoute', 'ui.utils', 'crmUi', 'crmResource'], +]; diff --git a/ang/crmExample.js b/ang/crmExample.js index d32d81d29a62..138ce8f12a5b 100644 --- a/ang/crmExample.js +++ b/ang/crmExample.js @@ -1,8 +1,6 @@ (function(angular, $, _) { - angular.module('crmExample', [ - 'crmUtil', 'ngRoute', 'ui.utils', 'crmUi' - ]); + angular.module('crmExample', CRM.angRequires('crmExample')); angular.module('crmExample').config([ '$routeProvider', diff --git a/ang/crmExample/example.html b/ang/crmExample/example.html index c9b6abd03f7d..5393e7c1a521 100644 --- a/ang/crmExample/example.html +++ b/ang/crmExample/example.html @@ -20,17 +20,16 @@
    - +
    - + - +
    -
    {{exampleForm[exName]|json}}
    diff --git a/ang/crmMailing.ang.php b/ang/crmMailing.ang.php new file mode 100644 index 000000000000..a3e140c6058a --- /dev/null +++ b/ang/crmMailing.ang.php @@ -0,0 +1,18 @@ + 'civicrm', + 'js' => [ + 'ang/crmMailing.js', + 'ang/crmMailing/*.js', + ], + 'css' => ['ang/crmMailing.css'], + 'partials' => ['ang/crmMailing'], + 'requires' => ['crmUtil', 'crmAttachment', 'crmAutosave', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService', 'crmResource'], +]; diff --git a/ang/crmMailing.js b/ang/crmMailing.js index 7945ae64293d..95148bf27b3f 100644 --- a/ang/crmMailing.js +++ b/ang/crmMailing.js @@ -1,8 +1,6 @@ (function (angular, $, _) { - angular.module('crmMailing', [ - 'crmUtil', 'crmAttachment', 'crmAutosave', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService' - ]); + angular.module('crmMailing', CRM.angRequires('crmMailing')); angular.module('crmMailing').config([ '$routeProvider', diff --git a/ang/crmMailing/BlockMailing.html b/ang/crmMailing/BlockMailing.html index abf890a40700..3f6c7ca44aa1 100644 --- a/ang/crmMailing/BlockMailing.html +++ b/ang/crmMailing/BlockMailing.html @@ -6,21 +6,8 @@ -->
    -
    -
    - - -
    +
    +
    @@ -49,20 +36,18 @@
    -
    -
    +
    +
    - + />
    diff --git a/ang/crmMailing/BlockPreview.html b/ang/crmMailing/BlockPreview.html index a9e69eff9f8e..6315466e83cf 100644 --- a/ang/crmMailing/BlockPreview.html +++ b/ang/crmMailing/BlockPreview.html @@ -22,7 +22,7 @@
    -->
    -
    +
    {{ts('Send test email to:')}} @@ -30,32 +30,28 @@
    + crm-multiple-email + />
    - +
    -
    +
    {{ts('Send test email to group:')}}
    - + />
    - +
    diff --git a/ang/crmMailing/BlockPreview.js b/ang/crmMailing/BlockPreview.js index 561ddf09501c..5e582dc0566a 100644 --- a/ang/crmMailing/BlockPreview.js +++ b/ang/crmMailing/BlockPreview.js @@ -21,6 +21,7 @@ }); }; scope.doSend = function doSend(recipient) { + recipient = JSON.parse(JSON.stringify(recipient).replace(/\,\s/g, ',')); scope.$eval(attr.onSend, { preview: {recipient: recipient} }); @@ -29,16 +30,15 @@ scope.previewTestGroup = function(e) { var $dialog = $(this); $dialog.html('
    ').parent().find('button[data-op=yes]').prop('disabled', true); - $dialog.dialog('option', 'title', ts('Send to %1', {1: _.pluck(_.where(scope.crmMailingConst.groupNames, {id: scope.testGroup.gid}), 'title')[0]})); - CRM.api3('contact', 'get', { - group: scope.testGroup.gid, - options: {limit: 0}, - return: 'display_name,email' + CRM.api3({ + contact: ['contact', 'get', {group: scope.testGroup.gid, options: {limit: 0}, return: 'display_name,email'}], + group: ['group', 'getsingle', {id: scope.testGroup.gid, return: 'title'}] }).done(function(data) { + $dialog.dialog('option', 'title', ts('Send to %1', {1: data.group.title})); var count = 0, // Fixme: should this be in a template? - markup = '
      '; - _.each(data.values, function(row) { + markup = '
        '; + _.each(data.contact.values, function(row) { // Fixme: contact api doesn't seem capable of filtering out contacts with no email, so we're doing it client-side if (row.email) { count++; @@ -49,7 +49,7 @@ markup = '

        ' + ts('A test message will be sent to %1 people:', {1: count}) + '

        ' + markup; if (!count) { markup = '
        ' + - (data.count ? ts('None of the contacts in this group have an email address.') : ts('Group is empty.')) + + (data.contact.count ? ts('None of the contacts in this group have an email address.') : ts('Group is empty.')) + '
        '; } $dialog diff --git a/ang/crmMailing/BlockRecipients.html b/ang/crmMailing/BlockRecipients.html index 3a82623aa348..cedfa6dc2441 100644 --- a/ang/crmMailing/BlockRecipients.html +++ b/ang/crmMailing/BlockRecipients.html @@ -1,19 +1,15 @@
        - - - + ng-required="true" /> + +
        + + {{getRecipientCount()}} +
        diff --git a/ang/crmMailing/BlockReview.html b/ang/crmMailing/BlockReview.html index def183c0109e..5a0021e40ab6 100644 --- a/ang/crmMailing/BlockReview.html +++ b/ang/crmMailing/BlockReview.html @@ -11,7 +11,7 @@
        - +
        ({{ts('Include:')}} {{getIncludesAsString(mailing)}})
        diff --git a/ang/crmMailing/BlockTemplates.html b/ang/crmMailing/BlockTemplates.html new file mode 100644 index 000000000000..38a37a91fd77 --- /dev/null +++ b/ang/crmMailing/BlockTemplates.html @@ -0,0 +1,9 @@ +
        + + +
        \ No newline at end of file diff --git a/ang/crmMailing/BlockTemplates.js b/ang/crmMailing/BlockTemplates.js new file mode 100644 index 000000000000..9ee2efbfeea8 --- /dev/null +++ b/ang/crmMailing/BlockTemplates.js @@ -0,0 +1,5 @@ +(function(angular, $, _) { + angular.module('crmMailing').directive('crmMailingBlockTemplates', function(crmMailingSimpleDirective) { + return crmMailingSimpleDirective('crmMailingBlockTemplates', '~/crmMailing/BlockTemplates.html'); + }); +})(angular, CRM.$, CRM._); \ No newline at end of file diff --git a/ang/crmMailing/BodyHtml.html b/ang/crmMailing/BodyHtml.html index e3de42005c02..b38c28c76bfa 100644 --- a/ang/crmMailing/BodyHtml.html +++ b/ang/crmMailing/BodyHtml.html @@ -4,13 +4,14 @@
        - +